@tokenbuddy/tb-admin 1.0.35 → 1.0.37

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 (93) hide show
  1. package/dist/src/cli.js +92 -19
  2. package/dist/src/config.d.ts +7 -1
  3. package/dist/src/config.js +16 -4
  4. package/dist/src/display-format.js +6 -14
  5. package/dist/src/init-command.d.ts +50 -0
  6. package/dist/src/init-command.js +347 -0
  7. package/dist/src/providers/fly-io.d.ts +3 -0
  8. package/dist/src/providers/fly-io.js +137 -0
  9. package/dist/src/providers/provider-definition.d.ts +38 -0
  10. package/dist/src/providers/provider-definition.js +2 -0
  11. package/dist/src/seller.d.ts +2 -0
  12. package/dist/src/seller.js +30 -13
  13. package/dist/src/server-cmd.d.ts +1 -0
  14. package/dist/src/server-cmd.js +9 -2
  15. package/dist/src/ui-actions.d.ts +3 -0
  16. package/dist/src/ui-actions.js +199 -27
  17. package/dist/src/ui-command.js +3 -2
  18. package/dist/src/ui-state.d.ts +1 -3
  19. package/dist/src/ui-state.js +4 -8
  20. package/dist/src/ui-static.js +43 -15
  21. package/dist/src/workdir.d.ts +21 -0
  22. package/dist/src/workdir.js +50 -0
  23. package/package.json +8 -2
  24. package/templates/providers/fly.io/admin.toml.example +18 -0
  25. package/templates/providers/fly.io/deploy-secrets/bootstrap/README.md +18 -0
  26. package/templates/providers/fly.io/deploy-secrets/bootstrap/admin-web.example.env +3 -0
  27. package/templates/providers/fly.io/deploy-secrets/bootstrap/cloudflare-r2.example.env +6 -0
  28. package/templates/providers/fly.io/deploy-secrets/bootstrap/registry-signing-key.example.json +6 -0
  29. package/templates/providers/fly.io/deploy-secrets/bootstrap/registry.example.json +14 -0
  30. package/templates/providers/fly.io/deploy-secrets/bootstrap/tb-registry.example.yaml +14 -0
  31. package/templates/providers/fly.io/deploy-secrets/seller-configs/README.md +13 -0
  32. package/templates/providers/fly.io/deploy-secrets/seller-configs/seller.example.yaml +35 -0
  33. package/templates/providers/fly.io/env/deploy.env.example +12 -0
  34. package/templates/providers/fly.io/fly/fly.tb-registry.toml +31 -0
  35. package/templates/providers/fly.io/fly/fly.tb-seller.toml +25 -0
  36. package/templates/providers/fly.io/provider.toml.example +10 -0
  37. package/dist/src/bootstrap-registry.d.ts.map +0 -1
  38. package/dist/src/bootstrap-registry.js.map +0 -1
  39. package/dist/src/cli.d.ts.map +0 -1
  40. package/dist/src/cli.js.map +0 -1
  41. package/dist/src/client.d.ts.map +0 -1
  42. package/dist/src/client.js.map +0 -1
  43. package/dist/src/config.d.ts.map +0 -1
  44. package/dist/src/config.js.map +0 -1
  45. package/dist/src/display-format.d.ts.map +0 -1
  46. package/dist/src/display-format.js.map +0 -1
  47. package/dist/src/index.d.ts.map +0 -1
  48. package/dist/src/index.js.map +0 -1
  49. package/dist/src/provider.d.ts.map +0 -1
  50. package/dist/src/provider.js.map +0 -1
  51. package/dist/src/seller.d.ts.map +0 -1
  52. package/dist/src/seller.js.map +0 -1
  53. package/dist/src/server-cmd.d.ts.map +0 -1
  54. package/dist/src/server-cmd.js.map +0 -1
  55. package/dist/src/ui-actions.d.ts.map +0 -1
  56. package/dist/src/ui-actions.js.map +0 -1
  57. package/dist/src/ui-command.d.ts.map +0 -1
  58. package/dist/src/ui-command.js.map +0 -1
  59. package/dist/src/ui-server.d.ts.map +0 -1
  60. package/dist/src/ui-server.js.map +0 -1
  61. package/dist/src/ui-state.d.ts.map +0 -1
  62. package/dist/src/ui-state.js.map +0 -1
  63. package/dist/src/ui-static.d.ts.map +0 -1
  64. package/dist/src/ui-static.js.map +0 -1
  65. package/dist/src/upstream-balance-probe.d.ts.map +0 -1
  66. package/dist/src/upstream-balance-probe.js.map +0 -1
  67. package/dist/src/vendor-client.d.ts.map +0 -1
  68. package/dist/src/vendor-client.js.map +0 -1
  69. package/dist/src/vendor-commands.d.ts.map +0 -1
  70. package/dist/src/vendor-commands.js.map +0 -1
  71. package/src/bootstrap-registry.ts +0 -90
  72. package/src/cli.ts +0 -1614
  73. package/src/client.ts +0 -179
  74. package/src/config.ts +0 -194
  75. package/src/display-format.ts +0 -411
  76. package/src/index.ts +0 -11
  77. package/src/provider.ts +0 -150
  78. package/src/seller.ts +0 -538
  79. package/src/server-cmd.ts +0 -362
  80. package/src/ui-actions.ts +0 -1040
  81. package/src/ui-command.ts +0 -44
  82. package/src/ui-server.ts +0 -353
  83. package/src/ui-state.ts +0 -1318
  84. package/src/ui-static.ts +0 -673
  85. package/src/upstream-balance-probe.ts +0 -13
  86. package/src/vendor-client.ts +0 -23
  87. package/src/vendor-commands.ts +0 -65
  88. package/tests/admin.test.ts +0 -2162
  89. package/tests/seller.test.ts +0 -388
  90. package/tests/ui-state-fleet.test.ts +0 -526
  91. package/tests/ui-static-row.test.ts +0 -467
  92. package/tests/vendor-cli.test.ts +0 -241
  93. package/tsconfig.json +0 -8
package/src/client.ts DELETED
@@ -1,179 +0,0 @@
1
- /**
2
- * admin CLI 调 wallet-bootstrap / seller 服务的轻量 HTTP 客户端。
3
- * 内部封装 Bearer Token 鉴权、JSON 序列化、非 2xx 抛错的统一行为。
4
- */
5
- export class AdminClient {
6
- protected baseUrl: string;
7
- protected token: string;
8
-
9
- constructor(baseUrl: string, token: string) {
10
- this.baseUrl = baseUrl.replace(/\/+$/, "");
11
- this.token = token;
12
- }
13
-
14
- private async request(path: string, method: string, body?: any, extraHeaders: { [key: string]: string } = {}): Promise<any> {
15
- const url = `${this.baseUrl}${path}`;
16
- const headers: { [key: string]: string } = {
17
- "Content-Type": "application/json",
18
- "Authorization": `Bearer ${this.token}`,
19
- ...extraHeaders
20
- };
21
-
22
- const options: RequestInit = {
23
- method,
24
- headers
25
- };
26
-
27
- if (body) {
28
- options.body = JSON.stringify(body);
29
- }
30
-
31
- try {
32
- const response = await fetch(url, options);
33
- if (!response.ok) {
34
- const errorText = await response.text();
35
- throw new Error(`HTTP Error ${response.status}: ${errorText || response.statusText}`);
36
- }
37
- const text = await response.text();
38
- return text ? JSON.parse(text) : {};
39
- } catch (err: any) {
40
- throw new Error(`Connection failed: ${err.message}`);
41
- }
42
- }
43
-
44
- public async get(path: string): Promise<any> {
45
- return this.request(path, "GET");
46
- }
47
-
48
- public async put(path: string, body?: any, headers?: { [key: string]: string }): Promise<any> {
49
- return this.request(path, "PUT", body, headers);
50
- }
51
-
52
- public async post(path: string, body?: any, headers?: { [key: string]: string }): Promise<any> {
53
- return this.request(path, "POST", body, headers);
54
- }
55
-
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);
62
- }
63
- }
64
-
65
- /**
66
- * Vendor domain client. Step 5 of the registry redesign: vendor
67
- * tokens call `/platform/*` (Bearer auth). The old `bootstrap
68
- * config*`, `bootstrap registry publish`, and `bootstrap default-
69
- * seller set` commands have been removed; vendors stage sellers and
70
- * submit release requests instead.
71
- */
72
- export class RegistryVendorClient extends AdminClient {
73
- public async me(): Promise<{ vendor: { id: string; name: string; status: string } }> {
74
- return this.get("/platform/me");
75
- }
76
-
77
- public async listSellers(): Promise<{ sellers: unknown[] }> {
78
- return this.get("/platform/sellers");
79
- }
80
-
81
- /**
82
- * Step 14 (v1.1): vendor-scoped seller status mutation.
83
- * Replaces the old `tb-admin bootstrap sellers status` path which called
84
- * /operator/registry/sellers/:id/status (operator-level, super-admin scope).
85
- * Vendor can only mutate sellers in its own scope; the platform vendor
86
- * (DEFAULT_PLATFORM_VENDOR_ID) can mutate any.
87
- */
88
- public async setSellerStatus(
89
- id: string,
90
- status: "active" | "draining" | "offline",
91
- options: { expectedVersion?: number; idempotencyKey?: string } = {}
92
- ): Promise<{ registry: { version: number; sellers: unknown[] } }> {
93
- const headers: { [key: string]: string } = {};
94
- if (options.idempotencyKey) {
95
- headers["Idempotency-Key"] = options.idempotencyKey;
96
- }
97
- return this.put(
98
- `/platform/sellers/${encodeURIComponent(id)}/status`,
99
- {
100
- status,
101
- ...(options.expectedVersion !== undefined ? { expectedVersion: options.expectedVersion } : {})
102
- },
103
- headers
104
- );
105
- }
106
-
107
- public async stageSeller(seller: unknown): Promise<{ pendingSeller: unknown }> {
108
- return this.post("/platform/sellers/stage", { seller });
109
- }
110
-
111
- public async listPendingSellers(status?: string): Promise<{ pendingSellers: unknown[] }> {
112
- const path = status ? `/platform/sellers/pending?status=${encodeURIComponent(status)}` : "/platform/sellers/pending";
113
- return this.get(path);
114
- }
115
-
116
- public async submitRelease(input: { stagedSellerIds: string[]; note?: string }): Promise<{ releaseRequest: unknown }> {
117
- return this.post("/platform/release-requests", input);
118
- }
119
-
120
- public async listReleaseRequests(limit = 20): Promise<{ releaseRequests: unknown[] }> {
121
- return this.get(`/platform/release-requests?limit=${limit}`);
122
- }
123
-
124
- public async getReleaseRequest(id: number): Promise<{ releaseRequest: unknown }> {
125
- return this.get(`/platform/release-requests/${id}`);
126
- }
127
- }
128
-
129
- /**
130
- * Super-admin client. Used by automated scripts / CI to drive the
131
- * admin web endpoints (force-publish, registry versions, audit).
132
- * Step 5 keeps this narrow; the human-facing flow is the browser.
133
- */
134
- export class RegistryAdminClient extends AdminClient {
135
- constructor(baseUrl: string, superAdminKey: string) {
136
- // We pass the super-admin key as the "token" but override the
137
- // Authorization header below — super-admin uses the
138
- // `X-Registry-Admin-Key` header, not Bearer.
139
- super(baseUrl, superAdminKey);
140
- }
141
-
142
- protected overrideHeaders(headers: Record<string, string>): Record<string, string> {
143
- return { ...headers, "X-Registry-Admin-Key": this.token };
144
- }
145
-
146
- private async requestAdmin(path: string, method: string, body?: any): Promise<any> {
147
- const url = `${this.baseUrl}${path}`;
148
- const headers = this.overrideHeaders({ "Content-Type": "application/json" });
149
- const options: RequestInit = { method, headers };
150
- if (body) options.body = JSON.stringify(body);
151
- const response = await fetch(url, options);
152
- if (!response.ok) {
153
- const text = await response.text();
154
- throw new Error(`HTTP Error ${response.status}: ${text || response.statusText}`);
155
- }
156
- const text = await response.text();
157
- return text ? JSON.parse(text) : {};
158
- }
159
-
160
- public async listVendors(): Promise<{ vendors: unknown[] }> {
161
- return this.requestAdmin("/platform/vendors", "GET");
162
- }
163
-
164
- public async createVendor(name: string): Promise<{ vendor: unknown; token: string }> {
165
- return this.requestAdmin("/platform/vendors", "POST", { name });
166
- }
167
-
168
- public async forcePublish(releaseId: number): Promise<{ releaseRequest: unknown; publishedVersion: number | null }> {
169
- return this.requestAdmin(`/admin/api/release-requests/${releaseId}/force-publish`, "POST");
170
- }
171
-
172
- public async listVersions(): Promise<{ versions: unknown[] }> {
173
- return this.requestAdmin("/admin/api/registry-versions", "GET");
174
- }
175
-
176
- public async listAudit(): Promise<{ records: unknown[] }> {
177
- return this.requestAdmin("/admin/api/audit", "GET");
178
- }
179
- }
package/src/config.ts DELETED
@@ -1,194 +0,0 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import * as os from "os";
4
- import TOML from "@iarna/toml";
5
-
6
- /**
7
- * admin CLI 单个 profile:远程 wallet-bootstrap / seller 服务的 URL + 鉴权 token。
8
- */
9
- export interface AdminProfile {
10
- /** 服务 base URL(如 `https://bootstrap.example.com`) */
11
- url: string;
12
- /** 鉴权 token(脱敏写入配置文件) */
13
- token: string;
14
- }
15
-
16
- /**
17
- * seller provider(如 Fly.io)的配置。
18
- * 字段命名采用 snake_case 匹配 TOML 习惯。
19
- */
20
- export interface SellerProviderConfig {
21
- /** Fly.io API token */
22
- token?: string;
23
- /** flyctl 可执行路径(默认从 PATH 解析) */
24
- flyctl_path?: string;
25
- /** 默认 seller app 名称(创建新 seller 时复用) */
26
- default_app?: string;
27
- /** 默认部署 region(如 `sin` / `nrt`) */
28
- default_region?: string;
29
- /** 默认 Docker 镜像 */
30
- default_image?: string;
31
- /** 默认 seller config YAML 路径 */
32
- default_config?: string;
33
- /** seller operator 接口的 secret(写盘 / 重启用) */
34
- operator_secret?: string;
35
- /** 持久化 volume 名 */
36
- volume_name?: string;
37
- /** volume 容量(GB) */
38
- volume_size_gb?: number;
39
- /** 已存在的 volume ID(attach 用) */
40
- volume_id?: string;
41
- /** volume snapshot 保留天数 */
42
- volume_snapshot_retention_days?: number;
43
- }
44
-
45
- /**
46
- * admin CLI 配置根结构。
47
- * `default_profile` 缺省时使用第一个 profile。
48
- */
49
- export interface AdminConfig {
50
- default_profile?: string;
51
- profiles: { [name: string]: AdminProfile };
52
- seller_providers?: { [name: string]: SellerProviderConfig };
53
- }
54
-
55
- /**
56
- * 解析 admin CLI 的默认配置路径:`~/.config/tokenbuddy/admin.toml`。
57
- * 兼容旧版 `~/.tokenbuddy/admin.json`(legacy JSON),由 `ConfigManager` 自动识别。
58
- *
59
- * @returns 配置文件绝对路径
60
- */
61
- export function getDefaultConfigPath(): string {
62
- const home = os.homedir();
63
- return path.join(home, ".config", "tokenbuddy", "admin.toml");
64
- }
65
-
66
- /**
67
- * admin CLI 配置的读写管理器(TOML 优先,legacy JSON 兼容)。
68
- * 写盘时自动 chmod 0600(best effort)保护含 token 的配置文件。
69
- */
70
- export class ConfigManager {
71
- private configPath: string;
72
-
73
- constructor(customPath?: string) {
74
- this.configPath = customPath || getDefaultConfigPath();
75
- }
76
-
77
- public getConfigPath(): string {
78
- return this.configPath;
79
- }
80
-
81
- public load(): AdminConfig {
82
- if (!fs.existsSync(this.configPath)) {
83
- return { profiles: {} };
84
- }
85
- try {
86
- const content = fs.readFileSync(this.configPath, "utf8");
87
-
88
- // Support legacy JSON format for backward compatibility
89
- if (this.configPath.endsWith(".json")) {
90
- return JSON.parse(content) as AdminConfig;
91
- }
92
-
93
- const parsed = TOML.parse(content) as any;
94
- // TOML nested tables [profiles.xxx] are parsed as { profiles: { xxx: { ... } } }
95
- return {
96
- default_profile: parsed.default_profile,
97
- profiles: parsed.profiles || {},
98
- seller_providers: parsed.seller_providers || undefined
99
- };
100
- } catch {
101
- return { profiles: {} };
102
- }
103
- }
104
-
105
- public save(config: AdminConfig): void {
106
- const parent = path.dirname(this.configPath);
107
- if (!fs.existsSync(parent)) {
108
- fs.mkdirSync(parent, { recursive: true });
109
- }
110
-
111
- if (this.configPath.endsWith(".json")) {
112
- // Legacy JSON format
113
- fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2), "utf8");
114
- } else {
115
- // TOML format (default)
116
- const tomlObj: any = {};
117
- if (config.default_profile) {
118
- tomlObj.default_profile = config.default_profile;
119
- }
120
- if (config.profiles) {
121
- tomlObj.profiles = config.profiles;
122
- }
123
- if (config.seller_providers) {
124
- tomlObj.seller_providers = config.seller_providers;
125
- }
126
- const content = TOML.stringify(tomlObj);
127
- fs.writeFileSync(this.configPath, content, "utf8");
128
-
129
- // chmod 0600 for security (contains tokens) - match Rust behavior
130
- try {
131
- fs.chmodSync(this.configPath, 0o600);
132
- } catch {
133
- // Ignore on platforms that don't support chmod
134
- }
135
- }
136
- }
137
-
138
- public getProfile(name?: string): AdminProfile | undefined {
139
- const config = this.load();
140
- const targetName = name || config.default_profile;
141
- if (!targetName) return undefined;
142
- return config.profiles[targetName];
143
- }
144
-
145
- public setProfile(name: string, profile: AdminProfile): void {
146
- const config = this.load();
147
- config.profiles[name] = profile;
148
- if (!config.default_profile) {
149
- config.default_profile = name;
150
- }
151
- this.save(config);
152
- }
153
-
154
- public removeProfile(name: string): void {
155
- const config = this.load();
156
- if (!config.profiles[name]) {
157
- throw new Error(`Profile \`${name}\` does not exist.`);
158
- }
159
- delete config.profiles[name];
160
- if (config.default_profile === name) {
161
- const remaining = Object.keys(config.profiles);
162
- config.default_profile = remaining.length > 0 ? remaining[0] : undefined;
163
- }
164
- this.save(config);
165
- }
166
-
167
- public useProfile(name: string): void {
168
- const config = this.load();
169
- if (!config.profiles[name]) {
170
- throw new Error(`Profile \`${name}\` does not exist.`);
171
- }
172
- config.default_profile = name;
173
- this.save(config);
174
- }
175
-
176
- public listProfiles(): string[] {
177
- const config = this.load();
178
- return Object.keys(config.profiles);
179
- }
180
-
181
- public getSellerProvider(name: string = "fly"): SellerProviderConfig | undefined {
182
- const config = this.load();
183
- return config.seller_providers?.[name];
184
- }
185
-
186
- public setSellerProvider(name: string, provider: SellerProviderConfig): void {
187
- const config = this.load();
188
- if (!config.seller_providers) {
189
- config.seller_providers = {};
190
- }
191
- config.seller_providers[name] = provider;
192
- this.save(config);
193
- }
194
- }