@tokenbuddy/tb-admin 1.0.0

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.
@@ -0,0 +1,712 @@
1
+ import { Command } from "commander";
2
+ import { ConfigManager } from "./config.js";
3
+ import { AdminClient } from "./client.js";
4
+ import { FlyProvider } from "./server-cmd.js";
5
+ import { loadRegistryFile, validateRegistryDocument } from "./bootstrap-registry.js";
6
+ import Table from "cli-table3";
7
+ import * as fs from "fs";
8
+ import YAML from "js-yaml";
9
+ function requireString(value, field) {
10
+ const normalized = String(value || "").trim();
11
+ if (!normalized) {
12
+ throw new Error(`${field} is required`);
13
+ }
14
+ return normalized;
15
+ }
16
+ function positiveInteger(value, field) {
17
+ const raw = Number(value);
18
+ if (!Number.isInteger(raw) || raw < 1) {
19
+ throw new Error(`${field} must be >= 1`);
20
+ }
21
+ return raw;
22
+ }
23
+ function validateBootstrapConfigDocument(input) {
24
+ if (!input || typeof input !== "object") {
25
+ throw new Error("bootstrap config document is required");
26
+ }
27
+ if (!input.clawtip || typeof input.clawtip !== "object") {
28
+ throw new Error("clawtip config is required");
29
+ }
30
+ return {
31
+ bind: {
32
+ host: requireString(input.bind?.host || "0.0.0.0", "bind.host"),
33
+ port: positiveInteger(input.bind?.port || 8080, "bind.port")
34
+ },
35
+ clawtip: {
36
+ payTo: requireString(input.clawtip.payTo, "pay_to"),
37
+ sm4KeyBase64: requireString(input.clawtip.sm4KeyBase64, "sm4_key_base64"),
38
+ skillSlug: requireString(input.clawtip.skillSlug, "skill_slug"),
39
+ skillId: requireString(input.clawtip.skillId, "skill_id"),
40
+ description: requireString(input.clawtip.description, "description"),
41
+ resourceUrl: requireString(input.clawtip.resourceUrl, "resource_url"),
42
+ activationFeeFen: positiveInteger(input.clawtip.activationFeeFen, "activation_fee_fen"),
43
+ microsPerFen: positiveInteger(input.clawtip.microsPerFen, "micros_per_fen")
44
+ },
45
+ sellerRegistryPath: requireString(input.sellerRegistryPath, "seller_registry_path"),
46
+ allowLocalSellerUrls: Boolean(input.allowLocalSellerUrls)
47
+ };
48
+ }
49
+ function loadBootstrapConfigFile(filePath) {
50
+ const content = fs.readFileSync(filePath, "utf8");
51
+ const parsed = YAML.load(content);
52
+ return validateBootstrapConfigDocument(parsed);
53
+ }
54
+ function validateSellerConfigDocument(input) {
55
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
56
+ throw new Error("seller config document is required");
57
+ }
58
+ requireString(input.upstreamUrl, "upstreamUrl");
59
+ if (input.clawtip !== undefined && input.clawtip !== null) {
60
+ requireString(input.clawtip.payTo, "clawtip.payTo");
61
+ requireString(input.clawtip.sm4KeyBase64, "clawtip.sm4KeyBase64");
62
+ requireString(input.clawtip.skillSlug, "clawtip.skillSlug");
63
+ requireString(input.clawtip.skillId, "clawtip.skillId");
64
+ requireString(input.clawtip.description, "clawtip.description");
65
+ requireString(input.clawtip.resourceUrl, "clawtip.resourceUrl");
66
+ positiveInteger(input.clawtip.activationFeeFen ?? 1, "clawtip.activationFeeFen");
67
+ positiveInteger(input.clawtip.microsPerFen ?? 10000, "clawtip.microsPerFen");
68
+ }
69
+ return input;
70
+ }
71
+ function loadSellerConfigFile(filePath) {
72
+ const content = fs.readFileSync(filePath, "utf8");
73
+ const parsed = YAML.load(content);
74
+ return validateSellerConfigDocument(parsed);
75
+ }
76
+ function sellerConfigUpdateSummary(response, document) {
77
+ const updated = response.config || document;
78
+ return `path=${response.configPath || "memory"} upstream=${updated.upstreamUrl} allowMock=${Boolean(updated.allowMock)} clawtip=${updated.clawtip ? "configured" : "disabled"}`;
79
+ }
80
+ function collectOption(value, previous) {
81
+ previous.push(value);
82
+ return previous;
83
+ }
84
+ function optionalNumber(value, field) {
85
+ if (value === undefined || value === null || value === "") {
86
+ return undefined;
87
+ }
88
+ const parsed = Number(value);
89
+ if (!Number.isFinite(parsed)) {
90
+ throw new Error(`${field} must be a number`);
91
+ }
92
+ return parsed;
93
+ }
94
+ function parseModelAliases(values) {
95
+ const aliases = {};
96
+ for (const value of values || []) {
97
+ const index = value.indexOf("=");
98
+ if (index <= 0 || index === value.length - 1) {
99
+ throw new Error(`model alias must use alias=target format: ${value}`);
100
+ }
101
+ aliases[value.slice(0, index)] = value.slice(index + 1);
102
+ }
103
+ return aliases;
104
+ }
105
+ function loadYamlOrJsonFile(filePath) {
106
+ return YAML.load(fs.readFileSync(filePath, "utf8"));
107
+ }
108
+ export function buildAdminCli(configManager) {
109
+ const program = new Command();
110
+ program
111
+ .name("tb-admin")
112
+ .description("Remote admin CLI for TokenBuddy seller apps")
113
+ .version("1.0.0")
114
+ .option("--url <url>", "Remote seller core API url")
115
+ .option("--token <token>", "Operator Bearer token")
116
+ .option("--profile <profile>", "Use custom profile instead of default")
117
+ .option("--config <path>", "Use custom config file path");
118
+ // Helper to resolve client
119
+ function getClient() {
120
+ const opts = program.opts();
121
+ const configPath = opts.config;
122
+ const mgr = configPath ? new ConfigManager(configPath) : configManager;
123
+ const url = opts.url || process.env.TOKENBUDDY_ADMIN_URL;
124
+ const token = opts.token || process.env.TOKENBUDDY_ADMIN_TOKEN;
125
+ const profileName = opts.profile || process.env.TOKENBUDDY_ADMIN_PROFILE;
126
+ if (url && token) {
127
+ return new AdminClient(url, token);
128
+ }
129
+ const activeProfile = mgr.getProfile(profileName);
130
+ if (!activeProfile) {
131
+ throw new Error("No active profile found. Provide --url and --token, or set TOKENBUDDY_ADMIN_URL, or register config profiles.");
132
+ }
133
+ return new AdminClient(activeProfile.url, activeProfile.token);
134
+ }
135
+ function getBaseUrl() {
136
+ const opts = program.opts();
137
+ const configPath = opts.config;
138
+ const mgr = configPath ? new ConfigManager(configPath) : configManager;
139
+ const url = opts.url || process.env.TOKENBUDDY_ADMIN_URL;
140
+ const profileName = opts.profile || process.env.TOKENBUDDY_ADMIN_PROFILE;
141
+ if (url) {
142
+ return url.replace(/\/+$/, "");
143
+ }
144
+ const activeProfile = mgr.getProfile(profileName);
145
+ if (!activeProfile) {
146
+ throw new Error("No active profile found. Provide --url, set TOKENBUDDY_ADMIN_URL, or register config profiles.");
147
+ }
148
+ return activeProfile.url.replace(/\/+$/, "");
149
+ }
150
+ async function publicGet(path) {
151
+ const response = await fetch(`${getBaseUrl()}${path}`);
152
+ if (!response.ok) {
153
+ const errorText = await response.text();
154
+ throw new Error(`HTTP Error ${response.status}: ${errorText || response.statusText}`);
155
+ }
156
+ const text = await response.text();
157
+ return text ? JSON.parse(text) : {};
158
+ }
159
+ async function getSellerConfig(client) {
160
+ const data = await client.get("/operator/admin/config");
161
+ return validateSellerConfigDocument(data.config || data);
162
+ }
163
+ async function putSellerConfig(client, document) {
164
+ validateSellerConfigDocument(document);
165
+ return client.put("/operator/admin/config", { config: document });
166
+ }
167
+ // 1. Status Command
168
+ program
169
+ .command("status")
170
+ .description("Show status of the seller server")
171
+ .action(async () => {
172
+ try {
173
+ const client = getClient();
174
+ const data = await client.get("/operator/status");
175
+ console.log("=== Seller Server Operator Status ===");
176
+ console.log(JSON.stringify(data, null, 2));
177
+ }
178
+ catch (err) {
179
+ console.error("Error:", err.message);
180
+ process.exit(1);
181
+ }
182
+ });
183
+ // 2. Service Command
184
+ program
185
+ .command("service")
186
+ .description("Show admin service info")
187
+ .action(async () => {
188
+ try {
189
+ const client = getClient();
190
+ const data = await client.get("/operator/admin/service");
191
+ console.log("=== Seller Service Info ===");
192
+ console.log(JSON.stringify(data, null, 2));
193
+ }
194
+ catch (err) {
195
+ console.error("Error:", err.message);
196
+ process.exit(1);
197
+ }
198
+ });
199
+ // 3. Payments Group
200
+ const payments = program.command("payments").description("Manage payment methods");
201
+ payments
202
+ .command("list")
203
+ .description("List payment methods")
204
+ .action(async () => {
205
+ try {
206
+ const client = getClient();
207
+ const data = await client.get("/operator/admin/payments");
208
+ console.log("=== Configured Payments ===");
209
+ console.log(JSON.stringify(data, null, 2));
210
+ }
211
+ catch (err) {
212
+ console.error("Error:", err.message);
213
+ }
214
+ });
215
+ payments
216
+ .command("set-clawtip")
217
+ .description("Configure ClawTip payment parameters")
218
+ .requiredOption("--pay-to <pay_to>", "Pay target account")
219
+ .requiredOption("--sm4-key-base64 <key>", "JD SM4 key encoded in base64")
220
+ .requiredOption("--skill-slug <slug>", "Skill Slug")
221
+ .requiredOption("--skill-id <id>", "Skill ID")
222
+ .requiredOption("--description <desc>", "Order description")
223
+ .requiredOption("--resource-url <url>", "Resource verification url")
224
+ .option("--micros-per-fen <micros>", "Micros per Fen exchange ratio", "10000")
225
+ .action(async (options) => {
226
+ try {
227
+ const client = getClient();
228
+ const document = await getSellerConfig(client);
229
+ document.clawtip = {
230
+ payTo: options.payTo,
231
+ sm4KeyBase64: options.sm4KeyBase64,
232
+ skillSlug: options.skillSlug,
233
+ skillId: options.skillId,
234
+ description: options.description,
235
+ resourceUrl: options.resourceUrl,
236
+ activationFeeFen: 1,
237
+ microsPerFen: parseInt(options.microsPerFen, 10)
238
+ };
239
+ const data = await putSellerConfig(client, document);
240
+ console.log(`ClawTip parameters successfully set: ${sellerConfigUpdateSummary(data, document)}`);
241
+ }
242
+ catch (err) {
243
+ console.error("Error:", err.message);
244
+ }
245
+ });
246
+ payments
247
+ .command("clear-clawtip")
248
+ .description("Disable ClawTip payment parameters")
249
+ .action(async () => {
250
+ try {
251
+ const client = getClient();
252
+ const document = await getSellerConfig(client);
253
+ delete document.clawtip;
254
+ const data = await putSellerConfig(client, document);
255
+ console.log(`ClawTip parameters cleared: ${sellerConfigUpdateSummary(data, document)}`);
256
+ }
257
+ catch (err) {
258
+ console.error("Error:", err.message);
259
+ }
260
+ });
261
+ payments
262
+ .command("enable-mock")
263
+ .description("Enable mock payment for developers")
264
+ .action(async () => {
265
+ try {
266
+ const client = getClient();
267
+ const document = await getSellerConfig(client);
268
+ document.allowMock = true;
269
+ const data = await putSellerConfig(client, document);
270
+ console.log(`Mock payment enabled: ${sellerConfigUpdateSummary(data, document)}`);
271
+ }
272
+ catch (err) {
273
+ console.error("Error:", err.message);
274
+ }
275
+ });
276
+ payments
277
+ .command("disable-mock")
278
+ .description("Disable mock payment")
279
+ .action(async () => {
280
+ try {
281
+ const client = getClient();
282
+ const document = await getSellerConfig(client);
283
+ document.allowMock = false;
284
+ const data = await putSellerConfig(client, document);
285
+ console.log(`Mock payment disabled: ${sellerConfigUpdateSummary(data, document)}`);
286
+ }
287
+ catch (err) {
288
+ console.error("Error:", err.message);
289
+ }
290
+ });
291
+ // 4. Models Command
292
+ program
293
+ .command("models")
294
+ .description("List available upstream models")
295
+ .action(async () => {
296
+ try {
297
+ const client = getClient();
298
+ const data = await client.get("/operator/admin/upstreams");
299
+ const table = new Table({ head: ["Model ID", "Input Price/1M", "Output Price/1M", "Streaming"] });
300
+ for (const ups of data.upstreams || []) {
301
+ for (const model of ups.models || []) {
302
+ table.push([
303
+ model.id,
304
+ `${model.inputPriceMicrosPer1m} micros`,
305
+ `${model.outputPriceMicrosPer1m} micros`,
306
+ model.streaming ? "Yes" : "No"
307
+ ]);
308
+ }
309
+ }
310
+ console.log("=== Upstream Model Configurations ===");
311
+ console.log(table.toString());
312
+ }
313
+ catch (err) {
314
+ console.error("Error:", err.message);
315
+ }
316
+ });
317
+ const upstreams = program.command("upstreams").description("Manage seller upstream config through full seller config updates");
318
+ upstreams
319
+ .command("get")
320
+ .description("Fetch seller upstream config summary")
321
+ .action(async () => {
322
+ try {
323
+ const client = getClient();
324
+ const data = await client.get("/operator/admin/upstreams");
325
+ console.log(JSON.stringify(data, null, 2));
326
+ }
327
+ catch (err) {
328
+ console.error("Error:", err.message);
329
+ process.exit(1);
330
+ }
331
+ });
332
+ upstreams
333
+ .command("update")
334
+ .description("Update upstream fields by fetching and pushing the full seller config")
335
+ .option("--upstream-url <url>", "Upstream base URL")
336
+ .option("--api-key <key>", "Upstream API key")
337
+ .option("--chat-completions <state>", "chatCompletions capability")
338
+ .option("--responses <state>", "responses capability")
339
+ .option("--messages <state>", "messages capability")
340
+ .option("--markup-ratio <ratio>", "Markup ratio")
341
+ .option("--discount-ratio <ratio>", "Discount ratio")
342
+ .option("--models-file <path>", "YAML/JSON file containing an array of model configs")
343
+ .option("--clear-aliases", "Clear model aliases before applying --model-alias")
344
+ .option("--model-alias <alias=target>", "Add or update model alias", collectOption, [])
345
+ .action(async (options) => {
346
+ try {
347
+ const client = getClient();
348
+ const document = await getSellerConfig(client);
349
+ if (options.upstreamUrl) {
350
+ document.upstreamUrl = options.upstreamUrl;
351
+ }
352
+ if (options.apiKey !== undefined) {
353
+ document.upstreamApiKey = options.apiKey;
354
+ }
355
+ const capabilities = {
356
+ ...(document.upstreamCapabilities || {})
357
+ };
358
+ if (options.chatCompletions) {
359
+ capabilities.chatCompletions = options.chatCompletions;
360
+ }
361
+ if (options.responses) {
362
+ capabilities.responses = options.responses;
363
+ }
364
+ if (options.messages) {
365
+ capabilities.messages = options.messages;
366
+ }
367
+ if (Object.keys(capabilities).length > 0) {
368
+ document.upstreamCapabilities = capabilities;
369
+ }
370
+ const markupRatio = optionalNumber(options.markupRatio, "markupRatio");
371
+ if (markupRatio !== undefined) {
372
+ document.markupRatio = markupRatio;
373
+ }
374
+ const discountRatio = optionalNumber(options.discountRatio, "discountRatio");
375
+ if (discountRatio !== undefined) {
376
+ document.discountRatio = discountRatio;
377
+ }
378
+ if (options.modelsFile) {
379
+ const models = loadYamlOrJsonFile(options.modelsFile);
380
+ if (!Array.isArray(models)) {
381
+ throw new Error("models file must contain a YAML/JSON array");
382
+ }
383
+ document.models = models;
384
+ }
385
+ if (options.clearAliases) {
386
+ document.modelAliases = {};
387
+ }
388
+ const aliases = parseModelAliases(options.modelAlias);
389
+ if (Object.keys(aliases).length > 0) {
390
+ document.modelAliases = {
391
+ ...(document.modelAliases || {}),
392
+ ...aliases
393
+ };
394
+ }
395
+ const response = await putSellerConfig(client, document);
396
+ const updated = response.config || document;
397
+ console.log(`Updated upstream config: path=${response.configPath || "memory"} upstream=${updated.upstreamUrl} models=${Array.isArray(updated.models) ? updated.models.length : 0}`);
398
+ }
399
+ catch (err) {
400
+ console.error("Error:", err.message);
401
+ process.exit(1);
402
+ }
403
+ });
404
+ upstreams
405
+ .command("refresh")
406
+ .description("Ask seller to refresh its transient upstream model catalog")
407
+ .option("--auto-models", "Replace configured models from refreshed OpenRouter catalog")
408
+ .action(async (options) => {
409
+ try {
410
+ const client = getClient();
411
+ const response = await client.post("/operator/admin/upstreams/refresh", { autoModels: Boolean(options.autoModels) });
412
+ console.log(`Refreshed upstream catalog: path=${response.configPath || "memory"} models=${response.refreshedModels} autoModels=${Boolean(response.autoModels)}`);
413
+ }
414
+ catch (err) {
415
+ console.error("Error:", err.message);
416
+ process.exit(1);
417
+ }
418
+ });
419
+ // 5. Seller Runtime Config Command
420
+ const sellerConfig = program.command("seller-config").description("Manage seller runtime YAML config");
421
+ sellerConfig
422
+ .command("get")
423
+ .description("Fetch seller runtime config")
424
+ .action(async () => {
425
+ try {
426
+ const client = getClient();
427
+ const document = await getSellerConfig(client);
428
+ console.log(YAML.dump(document, { lineWidth: 120, noRefs: true, sortKeys: false }));
429
+ }
430
+ catch (err) {
431
+ console.error("Error:", err.message);
432
+ process.exit(1);
433
+ }
434
+ });
435
+ sellerConfig
436
+ .command("put")
437
+ .description("Update seller runtime config from YAML")
438
+ .requiredOption("--file <path>", "Seller runtime YAML config file")
439
+ .action(async (options) => {
440
+ try {
441
+ const document = loadSellerConfigFile(options.file);
442
+ const client = getClient();
443
+ const response = await putSellerConfig(client, document);
444
+ console.log(`Updated seller config: path=${response.configPath || "memory"} upstream=${response.config?.upstreamUrl || document.upstreamUrl}`);
445
+ }
446
+ catch (err) {
447
+ console.error("Error:", err.message);
448
+ process.exit(1);
449
+ }
450
+ });
451
+ sellerConfig
452
+ .command("validate")
453
+ .description("Validate seller runtime YAML config")
454
+ .requiredOption("--file <path>", "Seller runtime YAML config file")
455
+ .action((options) => {
456
+ try {
457
+ const document = loadSellerConfigFile(options.file);
458
+ console.log(`Seller config valid: upstream=${document.upstreamUrl} models=${Array.isArray(document.models) ? document.models.length : 0}`);
459
+ }
460
+ catch (err) {
461
+ console.error("Error:", err.message);
462
+ process.exit(1);
463
+ }
464
+ });
465
+ // 5. Billing Command
466
+ const billing = program.command("billing").description("Query payment and inference billing records");
467
+ billing
468
+ .command("purchases")
469
+ .description("List all remote token purchase / payment transactions")
470
+ .action(async () => {
471
+ try {
472
+ const client = getClient();
473
+ const data = await client.get("/operator/admin/purchases");
474
+ const table = new Table({ head: ["Purchase ID", "Provider", "Amount", "State", "Date"] });
475
+ for (const p of data.purchases || []) {
476
+ table.push([
477
+ p.purchase_id,
478
+ p.payment_provider,
479
+ `${p.amount_micros} micros`,
480
+ p.state,
481
+ p.created_at
482
+ ]);
483
+ }
484
+ console.log("=== Remote Token Purchase / Payment History ===");
485
+ console.log(table.toString());
486
+ }
487
+ catch (err) {
488
+ console.error("Error:", err.message);
489
+ }
490
+ });
491
+ billing
492
+ .command("requests")
493
+ .description("List all remote inference usage consumption logs")
494
+ .action(async () => {
495
+ try {
496
+ const client = getClient();
497
+ const data = await client.get("/operator/admin/requests");
498
+ const table = new Table({ head: ["Request ID", "Model", "State", "Reserved", "Settled", "Tokens (I/O)", "Date"] });
499
+ for (const r of data.requests || []) {
500
+ table.push([
501
+ r.request_id,
502
+ r.model,
503
+ r.state,
504
+ `${r.reserved_micros} micros`,
505
+ `${r.settled_micros} micros`,
506
+ `${r.prompt_tokens} / ${r.completion_tokens}`,
507
+ r.created_at
508
+ ]);
509
+ }
510
+ console.log("=== Inference Usage Consumption Ledger ===");
511
+ console.log(table.toString());
512
+ }
513
+ catch (err) {
514
+ console.error("Error:", err.message);
515
+ }
516
+ });
517
+ // 6. Bootstrap Registry Command
518
+ const bootstrap = program.command("bootstrap").description("Manage wallet bootstrap service");
519
+ const bootstrapSellers = bootstrap.command("sellers").description("Manage public seller registry");
520
+ const bootstrapConfig = bootstrap.command("config").description("Manage wallet bootstrap YAML config");
521
+ bootstrapSellers
522
+ .command("get")
523
+ .description("Fetch public seller registry")
524
+ .action(async () => {
525
+ try {
526
+ const data = await publicGet("/registry/sellers");
527
+ console.log(JSON.stringify(data, null, 2));
528
+ }
529
+ catch (err) {
530
+ console.error("Error:", err.message);
531
+ process.exit(1);
532
+ }
533
+ });
534
+ bootstrapSellers
535
+ .command("put")
536
+ .description("Update public seller registry")
537
+ .requiredOption("--file <path>", "Seller registry JSON file")
538
+ .action(async (options) => {
539
+ try {
540
+ const document = loadRegistryFile(options.file);
541
+ const client = getClient();
542
+ const response = await client.put("/operator/registry/sellers", document);
543
+ console.log(`Updated seller registry: version=${response.version} sellers=${response.sellers.length}`);
544
+ }
545
+ catch (err) {
546
+ console.error("Error:", err.message);
547
+ process.exit(1);
548
+ }
549
+ });
550
+ bootstrapSellers
551
+ .command("validate")
552
+ .description("Validate seller registry JSON")
553
+ .requiredOption("--file <path>", "Seller registry JSON file")
554
+ .action((options) => {
555
+ try {
556
+ const document = loadRegistryFile(options.file);
557
+ validateRegistryDocument(document);
558
+ console.log(`Seller registry valid: version=${document.version} sellers=${document.sellers.length}`);
559
+ }
560
+ catch (err) {
561
+ console.error("Error:", err.message);
562
+ process.exit(1);
563
+ }
564
+ });
565
+ bootstrapConfig
566
+ .command("get")
567
+ .description("Fetch wallet bootstrap runtime config")
568
+ .action(async () => {
569
+ try {
570
+ const client = getClient();
571
+ const data = await client.get("/operator/config");
572
+ console.log(YAML.dump(data, { lineWidth: 120, noRefs: true }));
573
+ }
574
+ catch (err) {
575
+ console.error("Error:", err.message);
576
+ process.exit(1);
577
+ }
578
+ });
579
+ bootstrapConfig
580
+ .command("put")
581
+ .description("Update wallet bootstrap runtime config from YAML")
582
+ .requiredOption("--file <path>", "Wallet bootstrap YAML config file")
583
+ .action(async (options) => {
584
+ try {
585
+ const document = loadBootstrapConfigFile(options.file);
586
+ const client = getClient();
587
+ const response = await client.put("/operator/config", document);
588
+ console.log(`Updated wallet bootstrap config: skillSlug=${response.clawtip.skillSlug} registry=${response.sellerRegistryPath}`);
589
+ }
590
+ catch (err) {
591
+ console.error("Error:", err.message);
592
+ process.exit(1);
593
+ }
594
+ });
595
+ bootstrapConfig
596
+ .command("validate")
597
+ .description("Validate wallet bootstrap YAML config")
598
+ .requiredOption("--file <path>", "Wallet bootstrap YAML config file")
599
+ .action((options) => {
600
+ try {
601
+ const document = loadBootstrapConfigFile(options.file);
602
+ console.log(`Wallet bootstrap config valid: skillSlug=${document.clawtip.skillSlug} registry=${document.sellerRegistryPath}`);
603
+ }
604
+ catch (err) {
605
+ console.error("Error:", err.message);
606
+ process.exit(1);
607
+ }
608
+ });
609
+ // 7. Config Command (Local)
610
+ const configCmd = program.command("config").description("Manage local admin profiles");
611
+ configCmd
612
+ .command("set <profileName>")
613
+ .description("Add or update an admin connection profile")
614
+ .option("--url <url>", "Remote seller core API url")
615
+ .option("--token <token>", "Bearer Operator secret token")
616
+ .action((profileName, options) => {
617
+ const opts = program.opts();
618
+ const configPath = opts.config;
619
+ const mgr = configPath ? new ConfigManager(configPath) : configManager;
620
+ const url = options.url || opts.url || process.env.TOKENBUDDY_ADMIN_URL;
621
+ const token = options.token || opts.token || process.env.TOKENBUDDY_ADMIN_TOKEN;
622
+ if (!url || !token) {
623
+ console.error("error: required option '--url <url>' and '--token <token>' not specified");
624
+ process.exit(1);
625
+ }
626
+ mgr.setProfile(profileName, { url, token });
627
+ console.log(`Profile \`${profileName}\` successfully configured.`);
628
+ });
629
+ configCmd
630
+ .command("use <profileName>")
631
+ .description("Switch default profile to select")
632
+ .action((profileName) => {
633
+ const opts = program.opts();
634
+ const configPath = opts.config;
635
+ const mgr = configPath ? new ConfigManager(configPath) : configManager;
636
+ try {
637
+ mgr.useProfile(profileName);
638
+ console.log(`Now using profile \`${profileName}\` by default.`);
639
+ }
640
+ catch (err) {
641
+ console.error("Error:", err.message);
642
+ }
643
+ });
644
+ configCmd
645
+ .command("list")
646
+ .description("List all configured profiles")
647
+ .action(() => {
648
+ const opts = program.opts();
649
+ const configPath = opts.config;
650
+ const mgr = configPath ? new ConfigManager(configPath) : configManager;
651
+ const profiles = mgr.listProfiles();
652
+ const config = mgr.load();
653
+ console.log("=== Configured Local Profiles ===");
654
+ for (const p of profiles) {
655
+ const isDefault = p === config.default_profile ? "* " : " ";
656
+ const details = config.profiles[p];
657
+ console.log(`${isDefault}${p} -> ${details.url}`);
658
+ }
659
+ });
660
+ // 8. Seller Command (Fly.io)
661
+ const sellerCmd = program.command("seller").description("Deploy and manage seller containers on Fly.io");
662
+ const flyProvider = new FlyProvider();
663
+ sellerCmd
664
+ .command("ls")
665
+ .description("List all deployed apps on Fly.io")
666
+ .action(() => {
667
+ try {
668
+ const out = flyProvider.listApps();
669
+ console.log(out);
670
+ }
671
+ catch (err) {
672
+ console.error("Error:", err.message);
673
+ }
674
+ });
675
+ sellerCmd
676
+ .command("create <name>")
677
+ .description("Deploy a new machines instance on Fly.io")
678
+ .option("--region <region>", "Deployment fly region (default: hkg)", "hkg")
679
+ .option("--image <image>", "Docker image name target")
680
+ .requiredOption("--operator-secret <secret>", "Operator secret to configure")
681
+ .option("--dry-run", "Dry run display without actual execution")
682
+ .action((name, options) => {
683
+ try {
684
+ const res = flyProvider.createSeller({
685
+ name,
686
+ region: options.region,
687
+ image: options.image,
688
+ operatorSecret: options.operatorSecret,
689
+ dryRun: options.dryRun
690
+ });
691
+ console.log(res);
692
+ }
693
+ catch (err) {
694
+ console.error("Error:", err.message);
695
+ }
696
+ });
697
+ sellerCmd
698
+ .command("remove <name>")
699
+ .description("Completely destroy a seller app on Fly.io")
700
+ .option("--dry-run", "Dry run")
701
+ .action((name, options) => {
702
+ try {
703
+ const res = flyProvider.removeSeller(name, options.dryRun);
704
+ console.log(res);
705
+ }
706
+ catch (err) {
707
+ console.error("Error:", err.message);
708
+ }
709
+ });
710
+ return program;
711
+ }
712
+ //# sourceMappingURL=cli.js.map