@tokenbuddy/tokenbuddy 1.0.36 → 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 (143) hide show
  1. package/dist/src/buyer-store.d.ts +6 -1
  2. package/dist/src/buyer-store.js +43 -4
  3. package/dist/src/cli.js +2 -2
  4. package/dist/src/daemon.d.ts +12 -0
  5. package/dist/src/daemon.js +791 -61
  6. package/dist/src/doctor-diagnostics.js +1 -6
  7. package/dist/src/provider-install.d.ts +2 -2
  8. package/dist/src/provider-install.js +248 -2
  9. package/dist/src/seller-catalog.d.ts +21 -0
  10. package/dist/src/seller-catalog.js +17 -0
  11. package/dist/src/seller-route-planner.d.ts +4 -1
  12. package/dist/src/seller-route-planner.js +3 -0
  13. package/dist/src/seller-routing-strategy.d.ts +3 -0
  14. package/dist/src/terminal-detect.d.ts +1 -1
  15. package/dist/src/terminal-detect.js +3 -2
  16. package/package.json +15 -2
  17. package/static/ui/assets/index-Djfl9tw5.js +271 -0
  18. package/static/ui/assets/index-DkfztCkn.css +1 -0
  19. package/static/ui/index.html +2 -2
  20. package/dist/src/buyer-store.d.ts.map +0 -1
  21. package/dist/src/buyer-store.js.map +0 -1
  22. package/dist/src/clawtip-bootstrap.d.ts.map +0 -1
  23. package/dist/src/clawtip-bootstrap.js.map +0 -1
  24. package/dist/src/cli.d.ts.map +0 -1
  25. package/dist/src/cli.js.map +0 -1
  26. package/dist/src/credit-tracker.d.ts.map +0 -1
  27. package/dist/src/credit-tracker.js.map +0 -1
  28. package/dist/src/daemon.d.ts.map +0 -1
  29. package/dist/src/daemon.js.map +0 -1
  30. package/dist/src/doctor-clawtip-wallet.d.ts.map +0 -1
  31. package/dist/src/doctor-clawtip-wallet.js.map +0 -1
  32. package/dist/src/doctor-diagnostics.d.ts.map +0 -1
  33. package/dist/src/doctor-diagnostics.js.map +0 -1
  34. package/dist/src/index.d.ts.map +0 -1
  35. package/dist/src/index.js.map +0 -1
  36. package/dist/src/init-clawtip-activation.d.ts.map +0 -1
  37. package/dist/src/init-clawtip-activation.js.map +0 -1
  38. package/dist/src/init-payment-options.d.ts.map +0 -1
  39. package/dist/src/init-payment-options.js.map +0 -1
  40. package/dist/src/init-setup.d.ts.map +0 -1
  41. package/dist/src/init-setup.js.map +0 -1
  42. package/dist/src/model-index.d.ts.map +0 -1
  43. package/dist/src/model-index.js.map +0 -1
  44. package/dist/src/package-update.d.ts.map +0 -1
  45. package/dist/src/package-update.js.map +0 -1
  46. package/dist/src/prewarm-cache.d.ts.map +0 -1
  47. package/dist/src/prewarm-cache.js.map +0 -1
  48. package/dist/src/prewarm-scheduler.d.ts.map +0 -1
  49. package/dist/src/prewarm-scheduler.js.map +0 -1
  50. package/dist/src/provider-install.d.ts.map +0 -1
  51. package/dist/src/provider-install.js.map +0 -1
  52. package/dist/src/provider-routing-config.d.ts.map +0 -1
  53. package/dist/src/provider-routing-config.js.map +0 -1
  54. package/dist/src/registry-trust.d.ts.map +0 -1
  55. package/dist/src/registry-trust.js.map +0 -1
  56. package/dist/src/route-failover.d.ts.map +0 -1
  57. package/dist/src/route-failover.js.map +0 -1
  58. package/dist/src/seller-catalog.d.ts.map +0 -1
  59. package/dist/src/seller-catalog.js.map +0 -1
  60. package/dist/src/seller-concurrency-limiter.d.ts.map +0 -1
  61. package/dist/src/seller-concurrency-limiter.js.map +0 -1
  62. package/dist/src/seller-metadata-cache.d.ts.map +0 -1
  63. package/dist/src/seller-metadata-cache.js.map +0 -1
  64. package/dist/src/seller-pool.d.ts.map +0 -1
  65. package/dist/src/seller-pool.js.map +0 -1
  66. package/dist/src/seller-route-planner.d.ts.map +0 -1
  67. package/dist/src/seller-route-planner.js.map +0 -1
  68. package/dist/src/seller-routing-config.d.ts.map +0 -1
  69. package/dist/src/seller-routing-config.js.map +0 -1
  70. package/dist/src/seller-routing-strategy.d.ts.map +0 -1
  71. package/dist/src/seller-routing-strategy.js.map +0 -1
  72. package/dist/src/stream-failover.d.ts.map +0 -1
  73. package/dist/src/stream-failover.js.map +0 -1
  74. package/dist/src/tb-clawtip-proof.d.ts.map +0 -1
  75. package/dist/src/tb-clawtip-proof.js.map +0 -1
  76. package/dist/src/tb-proxyd.d.ts.map +0 -1
  77. package/dist/src/tb-proxyd.js.map +0 -1
  78. package/dist/src/terminal-detect.d.ts.map +0 -1
  79. package/dist/src/terminal-detect.js.map +0 -1
  80. package/dist/src/terminal-image.d.ts.map +0 -1
  81. package/dist/src/terminal-image.js.map +0 -1
  82. package/src/buyer-store.ts +0 -1090
  83. package/src/clawtip-bootstrap.ts +0 -65
  84. package/src/cli.ts +0 -2243
  85. package/src/credit-tracker.ts +0 -295
  86. package/src/daemon.ts +0 -5475
  87. package/src/doctor-clawtip-wallet.ts +0 -95
  88. package/src/doctor-diagnostics.ts +0 -1026
  89. package/src/index.ts +0 -16
  90. package/src/init-clawtip-activation.ts +0 -695
  91. package/src/init-payment-options.ts +0 -373
  92. package/src/init-setup.ts +0 -165
  93. package/src/model-index.ts +0 -278
  94. package/src/package-update.ts +0 -311
  95. package/src/prewarm-cache.ts +0 -485
  96. package/src/prewarm-scheduler.ts +0 -675
  97. package/src/provider-install.ts +0 -1006
  98. package/src/provider-routing-config.ts +0 -410
  99. package/src/registry-trust.ts +0 -51
  100. package/src/route-failover.ts +0 -304
  101. package/src/seller-catalog.ts +0 -505
  102. package/src/seller-concurrency-limiter.ts +0 -161
  103. package/src/seller-metadata-cache.ts +0 -91
  104. package/src/seller-pool.ts +0 -557
  105. package/src/seller-route-planner.ts +0 -513
  106. package/src/seller-routing-config.ts +0 -211
  107. package/src/seller-routing-strategy.ts +0 -362
  108. package/src/stream-failover.ts +0 -152
  109. package/src/tb-clawtip-proof.ts +0 -28
  110. package/src/tb-proxyd.ts +0 -101
  111. package/src/terminal-detect.ts +0 -333
  112. package/src/terminal-image.ts +0 -228
  113. package/static/ui/assets/index-0MVXD7bH.css +0 -1
  114. package/static/ui/assets/index-BVbeDEwq.js +0 -271
  115. package/static/ui/assets/index-BVbeDEwq.js.map +0 -1
  116. package/tests/cli-routing.test.ts +0 -363
  117. package/tests/control-plane-ui-endpoints.test.ts +0 -1630
  118. package/tests/credit-tracker.test.ts +0 -165
  119. package/tests/daemon-413-fallback.test.ts +0 -92
  120. package/tests/daemon-classify.test.ts +0 -452
  121. package/tests/daemon-roles.test.ts +0 -92
  122. package/tests/daemon-trusted-registry-cache.test.ts +0 -132
  123. package/tests/e2e.test.ts +0 -366
  124. package/tests/image-generation-e2e.test.ts +0 -230
  125. package/tests/model-index.test.ts +0 -198
  126. package/tests/package-update.test.ts +0 -147
  127. package/tests/prewarm-cache.test.ts +0 -296
  128. package/tests/prewarm-scheduler.test.ts +0 -367
  129. package/tests/provider-routing-config.test.ts +0 -150
  130. package/tests/registry-trust.test.ts +0 -28
  131. package/tests/route-failover.test.ts +0 -222
  132. package/tests/seller-catalog-413.test.ts +0 -120
  133. package/tests/seller-catalog-utilities.test.ts +0 -124
  134. package/tests/seller-concurrency-limiter.test.ts +0 -83
  135. package/tests/seller-metadata-cache.test.ts +0 -89
  136. package/tests/seller-pool.test.ts +0 -365
  137. package/tests/seller-route-planner.test.ts +0 -312
  138. package/tests/seller-routing-config.test.ts +0 -124
  139. package/tests/seller-routing-strategy.test.ts +0 -167
  140. package/tests/stream-failover.test.ts +0 -52
  141. package/tests/thousand-seller.test.ts +0 -151
  142. package/tests/tokenbuddy.test.ts +0 -4043
  143. package/tsconfig.json +0 -8
@@ -1,363 +0,0 @@
1
- import * as fs from "fs";
2
- import * as os from "os";
3
- import * as path from "path";
4
- import { BuyerStore } from "../src/buyer-store.js";
5
- import { DEFAULT_CLAWTIP_BOOTSTRAP_URL } from "../src/clawtip-bootstrap.js";
6
- import { buildCli } from "../src/cli.js";
7
-
8
- describe("tb routing set", () => {
9
- let storeRoot: string;
10
- let previousStoreRoot: string | undefined;
11
- let logSpy: jest.SpyInstance;
12
-
13
- beforeEach(() => {
14
- storeRoot = fs.mkdtempSync(path.join(os.tmpdir(), "tokenbuddy-cli-routing-"));
15
- previousStoreRoot = process.env.TOKENBUDDY_BUYER_STORE;
16
- process.env.TOKENBUDDY_BUYER_STORE = storeRoot;
17
- logSpy = jest.spyOn(console, "log").mockImplementation(() => undefined);
18
- });
19
-
20
- afterEach(() => {
21
- logSpy.mockRestore();
22
- if (previousStoreRoot === undefined) {
23
- delete process.env.TOKENBUDDY_BUYER_STORE;
24
- } else {
25
- process.env.TOKENBUDDY_BUYER_STORE = previousStoreRoot;
26
- }
27
- fs.rmSync(storeRoot, { recursive: true, force: true });
28
- });
29
-
30
- test("fixed mode requires --seller", async () => {
31
- await expect(buildCli().parseAsync(["node", "tb", "routing", "set", "fixed"]))
32
- .rejects.toThrow("fixed routing requires --seller");
33
- });
34
-
35
- test("fixedSet mode requires --seller-set", async () => {
36
- await expect(buildCli().parseAsync(["node", "tb", "routing", "set", "fixedSet", "--seller-set", ""]))
37
- .rejects.toThrow("fixedSet routing requires --seller-set");
38
- });
39
-
40
- test("rejects unsupported routing mode", async () => {
41
- await expect(buildCli().parseAsync(["node", "tb", "routing", "set", "bogus"]))
42
- .rejects.toThrow("routing mode must be fixed, fixedSet, or fullAuto");
43
- });
44
-
45
- test("fullAuto accepts empty seller options and writes balanced config", async () => {
46
- await buildCli().parseAsync(["node", "tb", "routing", "set", "fullAuto"]);
47
-
48
- const store = new BuyerStore({ root: storeRoot });
49
- try {
50
- expect(store.getDaemonRuntimeConfig("routing")).toMatchObject({
51
- config: {
52
- mode: "fullAuto",
53
- scorer: "balanced",
54
- },
55
- });
56
- } finally {
57
- store.close();
58
- }
59
- });
60
-
61
- test("routing show prints fixed and fixedSet details in text mode", async () => {
62
- const output: string[] = [];
63
- logSpy.mockImplementation((message?: unknown) => {
64
- output.push(String(message));
65
- });
66
-
67
- await buildCli().parseAsync(["node", "tb", "routing", "set", "fixed", "--seller", "seller-a"]);
68
- output.length = 0;
69
- await buildCli().parseAsync(["node", "tb", "routing", "show"]);
70
- expect(output.join("\n")).toContain("Seller: seller-a");
71
- expect(output.join("\n")).toContain("Updated:");
72
-
73
- await buildCli().parseAsync(["node", "tb", "routing", "set", "fixedSet", "--seller-set", "seller-a,seller-b"]);
74
- output.length = 0;
75
- await buildCli().parseAsync(["node", "tb", "routing", "show"]);
76
- expect(output.join("\n")).toContain("Seller Set: seller-a,seller-b");
77
- expect(output.join("\n")).toContain("Updated:");
78
- });
79
-
80
- test("doctor rejects invalid configured control port", async () => {
81
- const previousControlPort = process.env.TB_PROXYD_CONTROL_PORT;
82
- try {
83
- process.env.TB_PROXYD_CONTROL_PORT = "not-a-port";
84
- await expect(buildCli().parseAsync(["node", "tb", "doctor", "--json"]))
85
- .rejects.toThrow("TB_PROXYD_CONTROL_PORT must be an integer port");
86
- } finally {
87
- restoreEnv("TB_PROXYD_CONTROL_PORT", previousControlPort);
88
- }
89
- });
90
-
91
- test("doctor --json reports daemon status and v1.2 snapshot", async () => {
92
- const output: string[] = [];
93
- logSpy.mockImplementation((message?: unknown) => {
94
- output.push(String(message));
95
- });
96
- const fetchMock = jest.spyOn(globalThis, "fetch").mockImplementation(async (url: string | URL | Request) => {
97
- const href = String(url);
98
- if (href.endsWith("/status")) {
99
- return response({
100
- pid: 123,
101
- sellerRoutingMode: "fixed",
102
- selectedSellerId: "seller-a",
103
- sellerRegistryUrl: "https://registry.example.test/sellers",
104
- });
105
- }
106
- if (href.endsWith("/v1.2/prewarm")) {
107
- return response({ pool: { entries: [] } });
108
- }
109
- if (href.endsWith("/health")) {
110
- return response({ ok: true });
111
- }
112
- if (href.endsWith("/routing/manual-providers")) {
113
- return response({
114
- providers: [{
115
- id: "manual-a",
116
- name: "Manual A",
117
- kind: "openai-compatible",
118
- baseUrl: "https://provider.example.test",
119
- models: ["gpt-test"],
120
- supportedProtocols: ["chat_completions"],
121
- enabled: true,
122
- createdAt: "2026-06-10T00:00:00.000Z",
123
- updatedAt: "2026-06-10T00:00:00.000Z",
124
- keyRef: { kind: "env", name: "TB_TEST_PROVIDER_KEY", configured: true },
125
- status: "healthy",
126
- }],
127
- });
128
- }
129
- return response({}, false, 404);
130
- }) as jest.MockedFunction<typeof fetch>;
131
-
132
- try {
133
- await buildCli().parseAsync(["node", "tb", "doctor", "--json"]);
134
- const parsed = JSON.parse(output[0]) as any;
135
- expect(parsed.daemon.running).toBe(true);
136
- expect(parsed.daemon.status.selectedSellerId).toBe("seller-a");
137
- expect(parsed.v12).toEqual({ pool: { entries: [] } });
138
- expect(parsed.manualProviders.count).toBe(1);
139
- expect(parsed.manualProviders.providers[0].keyRef.name).toBe("TB_TEST_PROVIDER_KEY");
140
- expect(output.join("\n")).not.toContain("sk-");
141
- } finally {
142
- fetchMock.mockRestore();
143
- }
144
- });
145
-
146
- test("doctor --json reports fetch failures as not running", async () => {
147
- const output: string[] = [];
148
- logSpy.mockImplementation((message?: unknown) => {
149
- output.push(String(message));
150
- });
151
- const fetchMock = jest.spyOn(globalThis, "fetch").mockRejectedValue(new Error("offline"));
152
-
153
- try {
154
- await buildCli().parseAsync(["node", "tb", "doctor", "--json"]);
155
- const parsed = JSON.parse(output[0]) as any;
156
- expect(parsed.daemon.running).toBe(false);
157
- expect(parsed.daemon.error).toContain("offline");
158
- expect(parsed.v12).toBeNull();
159
- } finally {
160
- fetchMock.mockRestore();
161
- }
162
- });
163
-
164
- test("doctor text mode renders running daemon details and v1.2 section", async () => {
165
- const output: string[] = [];
166
- logSpy.mockImplementation((message?: unknown) => {
167
- output.push(String(message));
168
- });
169
- const fetchMock = jest.spyOn(globalThis, "fetch").mockImplementation(async (url: string | URL | Request) => {
170
- const href = String(url);
171
- if (href.endsWith("/status")) {
172
- return response({
173
- pid: 123,
174
- controlPort: 17820,
175
- proxyPort: 17821,
176
- sellerRoutingMode: "fixed",
177
- selectedSellerId: "seller-a",
178
- sellerRegistryUrl: "https://registry.example.test/sellers",
179
- });
180
- }
181
- if (href.endsWith("/v1.2/prewarm")) {
182
- return response({
183
- focusSet: ["gpt-5.4"],
184
- prewarm: {
185
- size: 1,
186
- entries: [{
187
- modelId: "gpt-5.4",
188
- state: "ready",
189
- candidateCount: 2,
190
- warmedAt: Date.now(),
191
- ttlMs: 60000,
192
- consecutiveWarmingFailures: 0,
193
- }],
194
- },
195
- pool: {
196
- size: 1,
197
- entries: [{ sellerId: "seller-a", circuit: "open", healthScore: 10 }],
198
- },
199
- credit: {
200
- totalWastedMicros: 1,
201
- wastedSinceLastDoctorRun: 2,
202
- purchasesInLastMinute: 0,
203
- purchaseBudgetPerMinute: 5,
204
- perSeller: [{ sellerId: "seller-a", currentBalanceMicros: 10, leftoverCreditMicros: 1 }],
205
- },
206
- scheduler: {
207
- inFlight: 0,
208
- queueDepth: 0,
209
- totalSucceeded: 1,
210
- totalFailed: 0,
211
- },
212
- });
213
- }
214
- if (href.endsWith("/routing/manual-providers")) {
215
- return response({
216
- providers: [{
217
- id: "manual-a",
218
- name: "Manual A",
219
- kind: "openai-compatible",
220
- baseUrl: "https://provider.example.test",
221
- models: ["gpt-test"],
222
- supportedProtocols: ["chat_completions"],
223
- enabled: true,
224
- createdAt: "2026-06-10T00:00:00.000Z",
225
- updatedAt: "2026-06-10T00:00:00.000Z",
226
- keyRef: { kind: "secret", name: "local:manual-a", configured: true },
227
- status: "healthy",
228
- lastAccess: "2026-06-10T00:01:00.000Z",
229
- }],
230
- });
231
- }
232
- return response({}, false, 404);
233
- }) as jest.MockedFunction<typeof fetch>;
234
-
235
- try {
236
- await buildCli().parseAsync(["node", "tb", "doctor"]);
237
- const text = output.join("\n");
238
- expect(text).toContain("Daemon tb-proxyd is running");
239
- expect(text).toContain("Routing Mode: fixed");
240
- expect(text).toContain("Selected Seller: seller-a");
241
- expect(text).toContain("--- Manual Providers ---");
242
- expect(text).toContain("Manual providers: 1");
243
- expect(text).toContain("secret:local:manual-a configured");
244
- expect(text).not.toContain("sk-");
245
- expect(text).toContain("=== v1.2 Fallback Pipeline ===");
246
- expect(text).toContain("seller-a [open]");
247
- } finally {
248
- fetchMock.mockRestore();
249
- }
250
- });
251
-
252
- test("doctor text mode reports offline daemon and repair hint", async () => {
253
- const output: string[] = [];
254
- logSpy.mockImplementation((message?: unknown) => {
255
- output.push(String(message));
256
- });
257
- const fetchMock = jest.spyOn(globalThis, "fetch").mockRejectedValue(new Error("offline"));
258
-
259
- try {
260
- await buildCli().parseAsync(["node", "tb", "doctor"]);
261
- const text = output.join("\n");
262
- expect(text).toContain("Daemon tb-proxyd is NOT running");
263
- expect(text).toContain("tb doctor --fix");
264
- } finally {
265
- fetchMock.mockRestore();
266
- }
267
- });
268
-
269
- test("payment commands reject unsupported methods after daemon gate", async () => {
270
- const errors: string[] = [];
271
- const errorSpy = jest.spyOn(console, "error").mockImplementation((message?: unknown) => {
272
- errors.push(String(message));
273
- });
274
- const fetchMock = jest.spyOn(globalThis, "fetch").mockResolvedValue(response({ ok: true }));
275
- const previousExitCode = process.exitCode;
276
-
277
- try {
278
- process.exitCode = undefined;
279
- await buildCli().parseAsync(["node", "tb", "payment", "add", "card"]);
280
- await buildCli().parseAsync(["node", "tb", "payment", "remove", "card"]);
281
-
282
- expect(process.exitCode).toBe(1);
283
- expect(errors.join("\n")).toContain("Unsupported payment method: card");
284
- } finally {
285
- process.exitCode = previousExitCode;
286
- fetchMock.mockRestore();
287
- errorSpy.mockRestore();
288
- }
289
- });
290
-
291
- test("payment add clawtip uses the default registry control plane", async () => {
292
- const errors: string[] = [];
293
- const errorSpy = jest.spyOn(console, "error").mockImplementation((message?: unknown) => {
294
- errors.push(String(message));
295
- });
296
- const fetchMock = jest.spyOn(globalThis, "fetch").mockResolvedValue(response({
297
- activationFeeFen: 1,
298
- payment: {
299
- orderNo: "order_default_bootstrap",
300
- amountFen: 1,
301
- indicator: "indicator_default_bootstrap",
302
- payTo: "pay-to-test",
303
- encryptedData: "ciphertext",
304
- slug: "tb-registry",
305
- skillId: "si-tb-registry",
306
- description: "TokenBuddy ClawTip wallet activation",
307
- resourceUrl: DEFAULT_CLAWTIP_BOOTSTRAP_URL,
308
- }
309
- }));
310
- const previousExitCode = process.exitCode;
311
- const previousBootstrapUrl = process.env.TOKENBUDDY_BOOTSTRAP_URL;
312
-
313
- try {
314
- process.exitCode = undefined;
315
- delete process.env.TOKENBUDDY_BOOTSTRAP_URL;
316
- await buildCli().parseAsync(["node", "tb", "payment", "add", "clawtip"]);
317
-
318
- expect(process.exitCode).toBeUndefined();
319
- expect(errors).toEqual([]);
320
- expect(fetchMock).toHaveBeenCalledWith(
321
- `${DEFAULT_CLAWTIP_BOOTSTRAP_URL}/payments/clawtip/bootstrap`,
322
- expect.objectContaining({ method: "POST" })
323
- );
324
- const store = new BuyerStore({ root: storeRoot });
325
- try {
326
- expect(store.getPayment("clawtip")).toMatchObject({
327
- method: "clawtip",
328
- enabled: true,
329
- isDefault: true,
330
- config: {
331
- bootstrapUrl: DEFAULT_CLAWTIP_BOOTSTRAP_URL,
332
- orderNo: "order_default_bootstrap",
333
- resourceUrl: DEFAULT_CLAWTIP_BOOTSTRAP_URL
334
- }
335
- });
336
- } finally {
337
- store.close();
338
- }
339
- } finally {
340
- process.exitCode = previousExitCode;
341
- restoreEnv("TOKENBUDDY_BOOTSTRAP_URL", previousBootstrapUrl);
342
- fetchMock.mockRestore();
343
- errorSpy.mockRestore();
344
- }
345
- });
346
- });
347
-
348
- function restoreEnv(name: string, value: string | undefined): void {
349
- if (value === undefined) {
350
- delete process.env[name];
351
- } else {
352
- process.env[name] = value;
353
- }
354
- }
355
-
356
- function response(body: unknown, ok = true, status = 200): Response {
357
- return {
358
- ok,
359
- status,
360
- json: async () => body,
361
- text: async () => JSON.stringify(body),
362
- } as Response;
363
- }