deepline 0.3.329 → 0.3.331

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 (96) hide show
  1. package/dist/bundling-sources/sdk/src/billing-idempotency.ts +24 -0
  2. package/dist/bundling-sources/sdk/src/client.ts +22 -22
  3. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  4. package/dist/bundling-sources/shared_libs/security/safe-outbound-fetch.ts +10 -1
  5. package/dist/cli/{auth-MFFVOITR.mjs → auth-TVTO43JD.mjs} +4 -4
  6. package/dist/cli/auth-YYTF3NO4.js +31 -0
  7. package/dist/cli/{billing-LRBHWLIQ.mjs → billing-FBZ5CCU7.mjs} +36 -14
  8. package/dist/cli/{billing-V4Y4OVQ7.js → billing-Y4EX5FFA.js} +101 -79
  9. package/dist/cli/{chunk-VDIHLKGG.js → chunk-3AQ3L2NX.js} +4 -4
  10. package/dist/cli/{chunk-HVPRQGF4.mjs → chunk-4JYLNJMD.mjs} +1 -1
  11. package/dist/cli/{chunk-SJV5MTOJ.mjs → chunk-BXZN544J.mjs} +1 -1
  12. package/dist/cli/{chunk-MFQQINUY.mjs → chunk-CKAZY2JT.mjs} +2 -2
  13. package/dist/cli/{chunk-EROOAI5G.mjs → chunk-DU72PAVF.mjs} +1 -1
  14. package/dist/cli/{chunk-3RZJ3GEG.mjs → chunk-EGRTGSNS.mjs} +3 -3
  15. package/dist/cli/{chunk-XRYKLK56.js → chunk-ELDOV5XG.js} +45 -45
  16. package/dist/cli/{chunk-7T6DX5RK.mjs → chunk-HG5NWSN2.mjs} +62 -61
  17. package/dist/cli/{chunk-J2E5R2VH.js → chunk-KGZPY524.js} +60 -60
  18. package/dist/cli/{chunk-OUTIWGKY.js → chunk-KXWOO3GI.js} +26 -26
  19. package/dist/cli/{chunk-4FLEXYUC.js → chunk-L4CJXV7D.js} +8 -8
  20. package/dist/cli/{chunk-AD23W4S5.js → chunk-LFV2A7GB.js} +1 -1
  21. package/dist/cli/{chunk-U4CTHVN7.js → chunk-MDQOGPQY.js} +533 -532
  22. package/dist/cli/{chunk-75ZOTF3K.js → chunk-NHR3O4OV.js} +15 -15
  23. package/dist/cli/{chunk-MO2XSC5I.mjs → chunk-O52SKQQT.mjs} +4 -4
  24. package/dist/cli/{chunk-HI7TAWFS.mjs → chunk-PS5BFY2W.mjs} +5 -5
  25. package/dist/cli/{chunk-OZGU7P2S.mjs → chunk-QEI6SNAT.mjs} +38 -22
  26. package/dist/cli/{chunk-GCNER3HV.mjs → chunk-RI57LHLC.mjs} +1 -1
  27. package/dist/cli/{chunk-YNSCELIJ.mjs → chunk-RRJ6AA5L.mjs} +2 -2
  28. package/dist/cli/{chunk-ZXGUYTIT.js → chunk-RX2WWVZ7.js} +6 -6
  29. package/dist/cli/{chunk-VLDLJZ75.js → chunk-T7E5KIPE.js} +48 -48
  30. package/dist/cli/{chunk-FK7PW6BP.mjs → chunk-VEP3POWB.mjs} +1 -1
  31. package/dist/cli/{chunk-U2N4CYT7.js → chunk-XZG75ELG.js} +6 -6
  32. package/dist/cli/{chunk-KHZQFFOQ.js → chunk-YCYWPBRV.js} +2 -2
  33. package/dist/cli/{chunk-ERU2OR6F.mjs → chunk-YIYK57LW.mjs} +2 -2
  34. package/dist/cli/{chunk-GY2EEO2Y.js → chunk-ZGWURYZK.js} +51 -35
  35. package/dist/cli/client-46KHPTTQ.js +20 -0
  36. package/dist/cli/{client-BNLD775D.mjs → client-Q7J5R5PT.mjs} +3 -3
  37. package/dist/cli/{csv-7MVQECWG.mjs → csv-RVG3V4YX.mjs} +3 -3
  38. package/dist/cli/{csv-ZT6ZRQ3Z.js → csv-WBEF4KIZ.js} +12 -12
  39. package/dist/cli/{db-C4BMZXJK.js → db-4SPPGSEV.js} +29 -29
  40. package/dist/cli/{db-ABLWLMX5.mjs → db-AVIQ5WHJ.mjs} +4 -4
  41. package/dist/cli/{describe-XWOWAR32.mjs → describe-4UA6N4YX.mjs} +8 -8
  42. package/dist/cli/{describe-YALVN4WH.js → describe-IQ3FOLGV.js} +15 -15
  43. package/dist/cli/{executions-U4TNRP26.js → executions-3ZUK3YJB.js} +6 -6
  44. package/dist/cli/{executions-7IZVKQ3O.mjs → executions-VCULCPYR.mjs} +4 -4
  45. package/dist/cli/{feedback-NWMRT5W4.js → feedback-7YKDOMWW.js} +6 -6
  46. package/dist/cli/{feedback-GO5XPEKY.mjs → feedback-SZLTZXWC.mjs} +3 -3
  47. package/dist/cli/git-DCZP3E3P.js +23 -0
  48. package/dist/cli/{git-R53WS4WA.mjs → git-H4YS7TQW.mjs} +4 -4
  49. package/dist/cli/index.js +79 -79
  50. package/dist/cli/index.mjs +31 -31
  51. package/dist/cli/{legacy-noops-NJWM53DE.js → legacy-noops-GU7X3U2Y.js} +7 -7
  52. package/dist/cli/{legacy-noops-6QAZW7WJ.mjs → legacy-noops-K6U4YL3K.mjs} +4 -4
  53. package/dist/cli/{monitors-2N4BRU5M.mjs → monitors-2HHSSSDD.mjs} +4 -4
  54. package/dist/cli/{monitors-2I2MYAPS.js → monitors-IUEWN2VX.js} +85 -85
  55. package/dist/cli/{org-D3RHK2Q7.mjs → org-6NBBO7G3.mjs} +4 -4
  56. package/dist/cli/{org-XVNAE2JV.js → org-E5TYEFPX.js} +60 -60
  57. package/dist/cli/{play-L7MYJZ65.mjs → play-C2BQ4MJG.mjs} +7 -7
  58. package/dist/cli/play-MPDZPEYN.js +153 -0
  59. package/dist/cli/{providers-5BTDAZHN.js → providers-FM6LWHA4.js} +7 -7
  60. package/dist/cli/{providers-YM4E7WZD.mjs → providers-N2LG7Y3E.mjs} +4 -4
  61. package/dist/cli/{quickstart-ARPLJOLH.mjs → quickstart-GQQ6ODNU.mjs} +3 -3
  62. package/dist/cli/{quickstart-5A3FHAH3.js → quickstart-I4K6ZDU6.js} +5 -5
  63. package/dist/cli/search-6G6FUVTJ.js +26 -0
  64. package/dist/cli/{search-KKJEVV27.mjs → search-NKDQSOQ5.mjs} +5 -5
  65. package/dist/cli/{secrets-DVPVCSZD.js → secrets-BBI5NYLT.js} +13 -13
  66. package/dist/cli/{secrets-DGRJ4DO5.mjs → secrets-FGKGASDL.mjs} +4 -4
  67. package/dist/cli/{sessions-56EHXLZD.mjs → sessions-7MWWKIUT.mjs} +4 -4
  68. package/dist/cli/{sessions-VG4ATQ5Y.js → sessions-YUTDWUVR.js} +5 -5
  69. package/dist/cli/{settings-4U5XG7QI.mjs → settings-JGJWBESE.mjs} +7 -7
  70. package/dist/cli/{settings-BLOTIW2G.js → settings-U55P74SS.js} +74 -74
  71. package/dist/cli/{setup-RHJAHPOR.mjs → setup-7D577YQO.mjs} +6 -6
  72. package/dist/cli/{setup-L7AMAXCH.js → setup-XYIDQ46U.js} +41 -41
  73. package/dist/cli/skills-PXHET5MG.js +28 -0
  74. package/dist/cli/{skills-VU55YFCI.mjs → skills-Y6WBNBPF.mjs} +4 -4
  75. package/dist/cli/tools-BCSXPN5X.js +40 -0
  76. package/dist/cli/{tools-JS3YIDOH.mjs → tools-OPTIBRZP.mjs} +6 -6
  77. package/dist/cli/update-2NXG3NKT.js +28 -0
  78. package/dist/cli/{update-EHI5QUXM.mjs → update-HKJQ762M.mjs} +6 -6
  79. package/dist/cli/{workflow-BITLTLRF.mjs → workflow-2ZOG35IA.mjs} +5 -5
  80. package/dist/cli/{workflow-NE4SYRAT.js → workflow-EBIKBXXC.js} +15 -15
  81. package/dist/index.js +6 -6
  82. package/dist/index.mjs +3 -3
  83. package/dist/install-integrity.json +75 -74
  84. package/dist/release.d.mts +1 -1
  85. package/dist/release.d.ts +1 -1
  86. package/dist/release.js +2 -2
  87. package/dist/release.mjs +1 -1
  88. package/package.json +1 -1
  89. package/dist/cli/auth-W2MQM6GQ.js +0 -31
  90. package/dist/cli/client-5AIFBLKQ.js +0 -20
  91. package/dist/cli/git-YWRLAOBY.js +0 -23
  92. package/dist/cli/play-OYRHI7G3.js +0 -153
  93. package/dist/cli/search-VE3O3ZEM.js +0 -26
  94. package/dist/cli/skills-QB4OQGGP.js +0 -28
  95. package/dist/cli/tools-2CPFYNK7.js +0 -40
  96. package/dist/cli/update-WXP24GU5.js +0 -28
@@ -0,0 +1,24 @@
1
+ import { DeeplineError } from './errors.js';
2
+
3
+ export function requireBillingIdempotencyKey(value: string): string {
4
+ const normalized = value.trim();
5
+ let valid =
6
+ normalized.length > 0 && normalized.length <= 200 && normalized === value;
7
+ if (valid) {
8
+ try {
9
+ valid =
10
+ new Headers({ 'Idempotency-Key': value }).get('Idempotency-Key') ===
11
+ value;
12
+ } catch {
13
+ valid = false;
14
+ }
15
+ }
16
+ if (!valid) {
17
+ throw new DeeplineError(
18
+ 'Billing idempotencyKey must contain 1–200 characters with no leading or trailing whitespace and be valid HTTP header text.',
19
+ undefined,
20
+ 'INVALID_BILLING_IDEMPOTENCY_KEY',
21
+ );
22
+ }
23
+ return value;
24
+ }
@@ -51,6 +51,7 @@ import { playInputExampleArgs } from './plays/input-example.js';
51
51
  */
52
52
  import { resolveConfig } from './config.js';
53
53
  import { DeeplineError } from './errors.js';
54
+ import { requireBillingIdempotencyKey as requireTargetBillingIdempotencyKey } from './billing-idempotency.js';
54
55
  import {
55
56
  planCustomerDbQuery,
56
57
  queryCustomerDatabase,
@@ -2560,22 +2561,6 @@ export type BillingNamespace = {
2560
2561
  }) => Promise<{ url: string }>;
2561
2562
  };
2562
2563
 
2563
- function requireTargetBillingIdempotencyKey(value: string): string {
2564
- const normalized = value.trim();
2565
- if (
2566
- normalized.length === 0 ||
2567
- normalized.length > 200 ||
2568
- normalized !== value
2569
- ) {
2570
- throw new DeeplineError(
2571
- 'Billing idempotencyKey must contain 1–200 characters with no leading or trailing whitespace.',
2572
- undefined,
2573
- 'INVALID_BILLING_IDEMPOTENCY_KEY',
2574
- );
2575
- }
2576
- return normalized;
2577
- }
2578
-
2579
2564
  function isRecord(value: unknown): value is Record<string, unknown> {
2580
2565
  return Boolean(value && typeof value === 'object' && !Array.isArray(value));
2581
2566
  }
@@ -6275,15 +6260,21 @@ export class DeeplineClient {
6275
6260
  credits: number;
6276
6261
  idempotencyKey?: string;
6277
6262
  }): Promise<BillingTopUpResult> {
6263
+ const idempotencyKey =
6264
+ options.idempotencyKey === undefined
6265
+ ? undefined
6266
+ : requireTargetBillingIdempotencyKey(options.idempotencyKey);
6278
6267
  return this.http.post<BillingTopUpResult>(
6279
6268
  '/api/v2/billing/top-up',
6280
6269
  {
6281
6270
  credits: options.credits,
6282
- ...(options.idempotencyKey
6283
- ? { idempotency_key: options.idempotencyKey }
6271
+ ...(idempotencyKey !== undefined
6272
+ ? { idempotency_key: idempotencyKey }
6284
6273
  : {}),
6285
6274
  },
6286
- undefined,
6275
+ idempotencyKey === undefined
6276
+ ? undefined
6277
+ : { 'Idempotency-Key': idempotencyKey },
6287
6278
  // The idempotency key makes a retry at the product layer safe, but the
6288
6279
  // CLI should not hide ambiguous local delivery failures behind implicit
6289
6280
  // transport retries for a payment mutation.
@@ -6300,15 +6291,24 @@ export class DeeplineClient {
6300
6291
  credits: number;
6301
6292
  idempotencyKey?: string;
6302
6293
  }): Promise<BillingTopUpReceipt> {
6294
+ const idempotencyKey =
6295
+ options.idempotencyKey === undefined
6296
+ ? undefined
6297
+ : requireTargetBillingIdempotencyKey(options.idempotencyKey);
6303
6298
  return this.http.post<BillingTopUpReceipt>(
6304
6299
  '/api/v2/billing/top-up',
6305
6300
  {
6306
6301
  credits: options.credits,
6307
- ...(options.idempotencyKey
6308
- ? { idempotency_key: options.idempotencyKey }
6302
+ ...(idempotencyKey !== undefined
6303
+ ? { idempotency_key: idempotencyKey }
6304
+ : {}),
6305
+ },
6306
+ {
6307
+ 'x-deepline-top-up-receipt': 'operation-v1',
6308
+ ...(idempotencyKey !== undefined
6309
+ ? { 'Idempotency-Key': idempotencyKey }
6309
6310
  : {}),
6310
6311
  },
6311
- { 'x-deepline-top-up-receipt': 'operation-v1' },
6312
6312
  { maxRetries: 0, exactUrlOnly: true },
6313
6313
  );
6314
6314
  }
@@ -200,7 +200,7 @@ export const SDK_RELEASE = {
200
200
  // getters keep their established compatibility behavior.
201
201
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
202
202
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
203
- version: '0.3.329',
203
+ version: '0.3.331',
204
204
  updateSummary:
205
205
  'Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.',
206
206
  packageCapabilities: {
@@ -110,6 +110,15 @@ async function prepareBody(init: RequestInit): Promise<PreparedBody> {
110
110
  }
111
111
  return withContentLength(body.toString(), headers);
112
112
  }
113
+ if (typeof FormData !== 'undefined' && body instanceof FormData) {
114
+ const encoded = new Response(body);
115
+ const contentType = encoded.headers.get('content-type');
116
+ if (!contentType) {
117
+ throw new TypeError('Multipart encoding did not produce a content type.');
118
+ }
119
+ headers.set('content-type', contentType);
120
+ return withContentLength(Buffer.from(await encoded.arrayBuffer()), headers);
121
+ }
113
122
  if (body instanceof ArrayBuffer) {
114
123
  return withContentLength(Buffer.from(body), headers);
115
124
  }
@@ -124,7 +133,7 @@ async function prepareBody(init: RequestInit): Promise<PreparedBody> {
124
133
  }
125
134
 
126
135
  throw new TypeError(
127
- 'safeOutboundFetch does not support streaming or multipart request bodies.',
136
+ 'safeOutboundFetch does not support streaming request bodies.',
128
137
  );
129
138
  }
130
139
 
@@ -9,11 +9,11 @@ import {
9
9
  registerAuthCommands,
10
10
  reportSetupCompleted,
11
11
  resolvePendingAuthClaimForResume
12
- } from "./chunk-MFQQINUY.mjs";
13
- import "./chunk-EROOAI5G.mjs";
14
- import "./chunk-FK7PW6BP.mjs";
12
+ } from "./chunk-CKAZY2JT.mjs";
13
+ import "./chunk-DU72PAVF.mjs";
14
+ import "./chunk-VEP3POWB.mjs";
15
15
  import "./chunk-DLIRSHV6.mjs";
16
- import "./chunk-GCNER3HV.mjs";
16
+ import "./chunk-RI57LHLC.mjs";
17
17
  import "./chunk-NXGCO4JB.mjs";
18
18
  import "./chunk-WZYKRD7T.mjs";
19
19
  import "./chunk-KRD6H754.mjs";
@@ -0,0 +1,31 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+ var _chunkT7E5KIPEjs = require('./chunk-T7E5KIPE.js');
13
+ require('./chunk-XZG75ELG.js');
14
+ require('./chunk-3AQ3L2NX.js');
15
+ require('./chunk-XNYIZMNQ.js');
16
+ require('./chunk-LFV2A7GB.js');
17
+ require('./chunk-7P5AA3SN.js');
18
+ require('./chunk-OG64RBAC.js');
19
+ require('./chunk-COGTRVWW.js');
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+ exports.EXIT_WORKSPACE_MISMATCH = _chunkT7E5KIPEjs.EXIT_WORKSPACE_MISMATCH; exports.clearPendingClaim = _chunkT7E5KIPEjs.clearPendingClaim; exports.clearSatisfiedPendingClaim = _chunkT7E5KIPEjs.clearSatisfiedPendingClaim; exports.handleRegister = _chunkT7E5KIPEjs.handleRegister; exports.handleStatus = _chunkT7E5KIPEjs.handleStatus; exports.handleWait = _chunkT7E5KIPEjs.handleWait; exports.readPendingAuthClaim = _chunkT7E5KIPEjs.readPendingAuthClaim; exports.registerAuthCommands = _chunkT7E5KIPEjs.registerAuthCommands; exports.reportSetupCompleted = _chunkT7E5KIPEjs.reportSetupCompleted; exports.resolvePendingAuthClaimForResume = _chunkT7E5KIPEjs.resolvePendingAuthClaimForResume;
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  DeeplineClient,
3
- iterateCursorPages
4
- } from "./chunk-OZGU7P2S.mjs";
3
+ iterateCursorPages,
4
+ requireBillingIdempotencyKey
5
+ } from "./chunk-QEI6SNAT.mjs";
5
6
  import "./chunk-CBVIJSHF.mjs";
6
7
  import {
7
8
  createTimestampedName,
@@ -9,13 +10,13 @@ import {
9
10
  openInBrowser,
10
11
  printCommandEnvelope,
11
12
  shouldEmitJson
12
- } from "./chunk-EROOAI5G.mjs";
13
+ } from "./chunk-DU72PAVF.mjs";
13
14
  import {
14
15
  AuthError,
15
16
  ConfigError
16
- } from "./chunk-FK7PW6BP.mjs";
17
+ } from "./chunk-VEP3POWB.mjs";
17
18
  import "./chunk-DLIRSHV6.mjs";
18
- import "./chunk-GCNER3HV.mjs";
19
+ import "./chunk-RI57LHLC.mjs";
19
20
  import "./chunk-6IKARAYD.mjs";
20
21
  import {
21
22
  DeeplineError
@@ -89,11 +90,7 @@ function parseTopUpCredits(raw) {
89
90
  return parsed;
90
91
  }
91
92
  function topUpIdempotencyKey(raw) {
92
- if (typeof raw === "string") {
93
- const trimmed = raw.trim();
94
- if (trimmed) return trimmed.slice(0, 120);
95
- }
96
- return `cli_topup:${Date.now()}:${randomUUID()}`;
93
+ return raw === void 0 ? `cli_topup:${Date.now()}:${randomUUID()}` : requireBillingIdempotencyKey(raw);
97
94
  }
98
95
  function targetBillingIdempotencyKey(raw) {
99
96
  return typeof raw === "string" ? raw : `cli_target_billing:${Date.now()}:${randomUUID()}`;
@@ -362,14 +359,21 @@ async function handleBalance(options) {
362
359
  lines.push(`If the user approves, run: ${recovery.top_up_command}`);
363
360
  lines.push(`If checkout is required, run: ${recovery.checkout_command}`);
364
361
  }
365
- const text = [lines[0], ...options.verbose ? detail : [], ...lines.slice(1)];
362
+ const text = [
363
+ lines[0],
364
+ ...options.verbose ? detail : [],
365
+ ...lines.slice(1)
366
+ ];
366
367
  printCommandEnvelope(
367
368
  {
368
369
  ...payload,
369
370
  ...recovery ? { recovery } : {},
370
371
  render: {
371
372
  sections: [
372
- { title: "billing balance", lines: [lines[0], ...detail, ...lines.slice(1)] }
373
+ {
374
+ title: "billing balance",
375
+ lines: [lines[0], ...detail, ...lines.slice(1)]
376
+ }
373
377
  ]
374
378
  }
375
379
  },
@@ -961,7 +965,21 @@ async function handleTopUp(creditsRaw, options) {
961
965
  );
962
966
  return;
963
967
  }
964
- const idempotencyKey = topUpIdempotencyKey(options.idempotencyKey);
968
+ let idempotencyKey;
969
+ try {
970
+ idempotencyKey = topUpIdempotencyKey(options.idempotencyKey);
971
+ } catch (error) {
972
+ if (!(error instanceof DeeplineError)) throw error;
973
+ reportBillingFailure(
974
+ {
975
+ exitCode: 2,
976
+ code: "INVALID_BILLING_IDEMPOTENCY_KEY",
977
+ message: "--idempotency-key must contain 1\u2013200 characters with no leading or trailing whitespace and be valid HTTP header text."
978
+ },
979
+ options
980
+ );
981
+ return;
982
+ }
965
983
  if (options.dryRun) {
966
984
  printCommandEnvelope(
967
985
  {
@@ -972,6 +990,7 @@ async function handleTopUp(creditsRaw, options) {
972
990
  planned_request: {
973
991
  method: "POST",
974
992
  path: BILLING_TOP_UP_PATH,
993
+ headers: { "Idempotency-Key": idempotencyKey },
975
994
  body: { credits, idempotency_key: idempotencyKey }
976
995
  },
977
996
  render: {
@@ -1567,6 +1586,9 @@ Notes:
1567
1586
  workspace, then adding purchased Deepline credits. If no saved payment method
1568
1587
  is available, use the checkout command printed in the error response.
1569
1588
  --dry-run prints the planned request and idempotency key without charging.
1589
+ Earlier CLI versions truncated supplied keys over 120 characters. To recover
1590
+ an earlier attempt, reuse the effective key printed in its original output
1591
+ or retry command.
1570
1592
 
1571
1593
  Examples:
1572
1594
  deepline billing top-up 1000
@@ -1576,7 +1598,7 @@ Examples:
1576
1598
  `
1577
1599
  ).argument("<credits>", "Positive integer Deepline credit amount").option(
1578
1600
  "--idempotency-key <key>",
1579
- "Stable retry key for the same intended top-up"
1601
+ "Stable retry key for the same intended top-up (1\u2013200 characters, no padding, valid HTTP header text)"
1580
1602
  ).option("--dry-run", "Print the planned top-up without charging").option("--compact", "Keep only high-signal fields in JSON output").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(handleTopUp);
1581
1603
  billing.command("buy").description("Buy credits through the target billing contract.").argument("<credits>", "Positive integer Deepline credit amount").option("--idempotency-key <key>", "Stable retry key").option("--json", "Emit JSON output").action(handleBuyCredits);
1582
1604
  billing.command("top-up-recover").description(