@tokenbuddy/tokenbuddy 1.0.36 → 1.0.38

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 (146) hide show
  1. package/dist/src/buyer-store.d.ts +7 -2
  2. package/dist/src/buyer-store.js +46 -7
  3. package/dist/src/cli.d.ts +1 -0
  4. package/dist/src/cli.js +15 -7
  5. package/dist/src/daemon.d.ts +12 -0
  6. package/dist/src/daemon.js +791 -61
  7. package/dist/src/doctor-diagnostics.js +1 -6
  8. package/dist/src/provider-install.d.ts +2 -2
  9. package/dist/src/provider-install.js +248 -2
  10. package/dist/src/seller-catalog.d.ts +21 -0
  11. package/dist/src/seller-catalog.js +17 -0
  12. package/dist/src/seller-route-planner.d.ts +4 -1
  13. package/dist/src/seller-route-planner.js +3 -0
  14. package/dist/src/seller-routing-strategy.d.ts +3 -0
  15. package/dist/src/terminal-detect.d.ts +1 -1
  16. package/dist/src/terminal-detect.js +3 -2
  17. package/dist/src/workdir.d.ts +10 -0
  18. package/dist/src/workdir.js +26 -0
  19. package/package.json +15 -2
  20. package/static/ui/assets/index-Djfl9tw5.js +271 -0
  21. package/static/ui/assets/index-DkfztCkn.css +1 -0
  22. package/static/ui/index.html +2 -2
  23. package/dist/src/buyer-store.d.ts.map +0 -1
  24. package/dist/src/buyer-store.js.map +0 -1
  25. package/dist/src/clawtip-bootstrap.d.ts.map +0 -1
  26. package/dist/src/clawtip-bootstrap.js.map +0 -1
  27. package/dist/src/cli.d.ts.map +0 -1
  28. package/dist/src/cli.js.map +0 -1
  29. package/dist/src/credit-tracker.d.ts.map +0 -1
  30. package/dist/src/credit-tracker.js.map +0 -1
  31. package/dist/src/daemon.d.ts.map +0 -1
  32. package/dist/src/daemon.js.map +0 -1
  33. package/dist/src/doctor-clawtip-wallet.d.ts.map +0 -1
  34. package/dist/src/doctor-clawtip-wallet.js.map +0 -1
  35. package/dist/src/doctor-diagnostics.d.ts.map +0 -1
  36. package/dist/src/doctor-diagnostics.js.map +0 -1
  37. package/dist/src/index.d.ts.map +0 -1
  38. package/dist/src/index.js.map +0 -1
  39. package/dist/src/init-clawtip-activation.d.ts.map +0 -1
  40. package/dist/src/init-clawtip-activation.js.map +0 -1
  41. package/dist/src/init-payment-options.d.ts.map +0 -1
  42. package/dist/src/init-payment-options.js.map +0 -1
  43. package/dist/src/init-setup.d.ts.map +0 -1
  44. package/dist/src/init-setup.js.map +0 -1
  45. package/dist/src/model-index.d.ts.map +0 -1
  46. package/dist/src/model-index.js.map +0 -1
  47. package/dist/src/package-update.d.ts.map +0 -1
  48. package/dist/src/package-update.js.map +0 -1
  49. package/dist/src/prewarm-cache.d.ts.map +0 -1
  50. package/dist/src/prewarm-cache.js.map +0 -1
  51. package/dist/src/prewarm-scheduler.d.ts.map +0 -1
  52. package/dist/src/prewarm-scheduler.js.map +0 -1
  53. package/dist/src/provider-install.d.ts.map +0 -1
  54. package/dist/src/provider-install.js.map +0 -1
  55. package/dist/src/provider-routing-config.d.ts.map +0 -1
  56. package/dist/src/provider-routing-config.js.map +0 -1
  57. package/dist/src/registry-trust.d.ts.map +0 -1
  58. package/dist/src/registry-trust.js.map +0 -1
  59. package/dist/src/route-failover.d.ts.map +0 -1
  60. package/dist/src/route-failover.js.map +0 -1
  61. package/dist/src/seller-catalog.d.ts.map +0 -1
  62. package/dist/src/seller-catalog.js.map +0 -1
  63. package/dist/src/seller-concurrency-limiter.d.ts.map +0 -1
  64. package/dist/src/seller-concurrency-limiter.js.map +0 -1
  65. package/dist/src/seller-metadata-cache.d.ts.map +0 -1
  66. package/dist/src/seller-metadata-cache.js.map +0 -1
  67. package/dist/src/seller-pool.d.ts.map +0 -1
  68. package/dist/src/seller-pool.js.map +0 -1
  69. package/dist/src/seller-route-planner.d.ts.map +0 -1
  70. package/dist/src/seller-route-planner.js.map +0 -1
  71. package/dist/src/seller-routing-config.d.ts.map +0 -1
  72. package/dist/src/seller-routing-config.js.map +0 -1
  73. package/dist/src/seller-routing-strategy.d.ts.map +0 -1
  74. package/dist/src/seller-routing-strategy.js.map +0 -1
  75. package/dist/src/stream-failover.d.ts.map +0 -1
  76. package/dist/src/stream-failover.js.map +0 -1
  77. package/dist/src/tb-clawtip-proof.d.ts.map +0 -1
  78. package/dist/src/tb-clawtip-proof.js.map +0 -1
  79. package/dist/src/tb-proxyd.d.ts.map +0 -1
  80. package/dist/src/tb-proxyd.js.map +0 -1
  81. package/dist/src/terminal-detect.d.ts.map +0 -1
  82. package/dist/src/terminal-detect.js.map +0 -1
  83. package/dist/src/terminal-image.d.ts.map +0 -1
  84. package/dist/src/terminal-image.js.map +0 -1
  85. package/src/buyer-store.ts +0 -1090
  86. package/src/clawtip-bootstrap.ts +0 -65
  87. package/src/cli.ts +0 -2243
  88. package/src/credit-tracker.ts +0 -295
  89. package/src/daemon.ts +0 -5475
  90. package/src/doctor-clawtip-wallet.ts +0 -95
  91. package/src/doctor-diagnostics.ts +0 -1026
  92. package/src/index.ts +0 -16
  93. package/src/init-clawtip-activation.ts +0 -695
  94. package/src/init-payment-options.ts +0 -373
  95. package/src/init-setup.ts +0 -165
  96. package/src/model-index.ts +0 -278
  97. package/src/package-update.ts +0 -311
  98. package/src/prewarm-cache.ts +0 -485
  99. package/src/prewarm-scheduler.ts +0 -675
  100. package/src/provider-install.ts +0 -1006
  101. package/src/provider-routing-config.ts +0 -410
  102. package/src/registry-trust.ts +0 -51
  103. package/src/route-failover.ts +0 -304
  104. package/src/seller-catalog.ts +0 -505
  105. package/src/seller-concurrency-limiter.ts +0 -161
  106. package/src/seller-metadata-cache.ts +0 -91
  107. package/src/seller-pool.ts +0 -557
  108. package/src/seller-route-planner.ts +0 -513
  109. package/src/seller-routing-config.ts +0 -211
  110. package/src/seller-routing-strategy.ts +0 -362
  111. package/src/stream-failover.ts +0 -152
  112. package/src/tb-clawtip-proof.ts +0 -28
  113. package/src/tb-proxyd.ts +0 -101
  114. package/src/terminal-detect.ts +0 -333
  115. package/src/terminal-image.ts +0 -228
  116. package/static/ui/assets/index-0MVXD7bH.css +0 -1
  117. package/static/ui/assets/index-BVbeDEwq.js +0 -271
  118. package/static/ui/assets/index-BVbeDEwq.js.map +0 -1
  119. package/tests/cli-routing.test.ts +0 -363
  120. package/tests/control-plane-ui-endpoints.test.ts +0 -1630
  121. package/tests/credit-tracker.test.ts +0 -165
  122. package/tests/daemon-413-fallback.test.ts +0 -92
  123. package/tests/daemon-classify.test.ts +0 -452
  124. package/tests/daemon-roles.test.ts +0 -92
  125. package/tests/daemon-trusted-registry-cache.test.ts +0 -132
  126. package/tests/e2e.test.ts +0 -366
  127. package/tests/image-generation-e2e.test.ts +0 -230
  128. package/tests/model-index.test.ts +0 -198
  129. package/tests/package-update.test.ts +0 -147
  130. package/tests/prewarm-cache.test.ts +0 -296
  131. package/tests/prewarm-scheduler.test.ts +0 -367
  132. package/tests/provider-routing-config.test.ts +0 -150
  133. package/tests/registry-trust.test.ts +0 -28
  134. package/tests/route-failover.test.ts +0 -222
  135. package/tests/seller-catalog-413.test.ts +0 -120
  136. package/tests/seller-catalog-utilities.test.ts +0 -124
  137. package/tests/seller-concurrency-limiter.test.ts +0 -83
  138. package/tests/seller-metadata-cache.test.ts +0 -89
  139. package/tests/seller-pool.test.ts +0 -365
  140. package/tests/seller-route-planner.test.ts +0 -312
  141. package/tests/seller-routing-config.test.ts +0 -124
  142. package/tests/seller-routing-strategy.test.ts +0 -167
  143. package/tests/stream-failover.test.ts +0 -52
  144. package/tests/thousand-seller.test.ts +0 -151
  145. package/tests/tokenbuddy.test.ts +0 -4043
  146. 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
- }