@tarout/cli 0.10.0 → 0.11.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.
- package/dist/index.js +344 -116
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -62,7 +62,7 @@ import { Command } from "commander";
|
|
|
62
62
|
// package.json
|
|
63
63
|
var package_default = {
|
|
64
64
|
name: "@tarout/cli",
|
|
65
|
-
version: "0.
|
|
65
|
+
version: "0.11.0",
|
|
66
66
|
description: "Tarout CLI \u2014 the Saudi cloud platform for coding agents",
|
|
67
67
|
type: "module",
|
|
68
68
|
bin: {
|
|
@@ -753,8 +753,12 @@ don't hand-edit infrastructure.
|
|
|
753
753
|
- **Run locally** with cloud env vars: \`tarout dev\`.
|
|
754
754
|
- **Full agent guide:** https://tarout.sa/docs/for-ai/onboarding
|
|
755
755
|
|
|
756
|
-
Run Tarout commands with \`--json\` for machine-readable output.
|
|
757
|
-
|
|
756
|
+
Run Tarout commands with \`--json\` for machine-readable output. New apps and
|
|
757
|
+
databases automatically use your org's **subscribed tier** \u2014 don't pass
|
|
758
|
+
\`--plan free\` / \`--database-plan free\`. \`tarout login\` opens the browser for you;
|
|
759
|
+
the user just signs in. A \`NEEDS_UPGRADE\` error means the org is out of slots for
|
|
760
|
+
its tier: surface the two options (buy the add-on vs upgrade the plan) and let the
|
|
761
|
+
user pick \u2014 the chosen command opens the payment page and waits until it's confirmed.
|
|
758
762
|
Ask before destructive actions (delete, rollback, revealing secrets).
|
|
759
763
|
${BLOCK_END}`;
|
|
760
764
|
function markdownTargetFor(agent) {
|
|
@@ -3079,9 +3083,6 @@ function formatTime(ts) {
|
|
|
3079
3083
|
});
|
|
3080
3084
|
}
|
|
3081
3085
|
|
|
3082
|
-
// src/commands/auth.ts
|
|
3083
|
-
import open3 from "open";
|
|
3084
|
-
|
|
3085
3086
|
// src/lib/auth-server.ts
|
|
3086
3087
|
import { randomBytes, timingSafeEqual } from "crypto";
|
|
3087
3088
|
import express from "express";
|
|
@@ -3240,12 +3241,34 @@ function canLaunchBrowser() {
|
|
|
3240
3241
|
}
|
|
3241
3242
|
return Boolean(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
|
|
3242
3243
|
}
|
|
3244
|
+
async function openInBrowser(url, opts) {
|
|
3245
|
+
if (!isJsonMode()) {
|
|
3246
|
+
log(
|
|
3247
|
+
colors.dim(opts?.hint ?? "If the browser didn't open, visit this URL:")
|
|
3248
|
+
);
|
|
3249
|
+
log(` ${colors.cyan(url)}`);
|
|
3250
|
+
}
|
|
3251
|
+
if (opts?.noOpen || !canLaunchBrowser()) return false;
|
|
3252
|
+
try {
|
|
3253
|
+
await open2(url);
|
|
3254
|
+
return true;
|
|
3255
|
+
} catch {
|
|
3256
|
+
return false;
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3243
3259
|
function paymentBrowserOpener(opts) {
|
|
3244
3260
|
if (opts?.noOpen || !canLaunchBrowser()) return void 0;
|
|
3245
3261
|
return async (url) => {
|
|
3246
3262
|
try {
|
|
3247
3263
|
await open2(url);
|
|
3248
3264
|
} catch {
|
|
3265
|
+
if (!isJsonMode()) {
|
|
3266
|
+
log(
|
|
3267
|
+
colors.dim(
|
|
3268
|
+
"Couldn't open the browser automatically \u2014 use the payment link to complete checkout."
|
|
3269
|
+
)
|
|
3270
|
+
);
|
|
3271
|
+
}
|
|
3249
3272
|
}
|
|
3250
3273
|
};
|
|
3251
3274
|
}
|
|
@@ -3255,21 +3278,24 @@ function shouldAutoConfirmPaidCheckout(amountDueHalalas) {
|
|
|
3255
3278
|
}
|
|
3256
3279
|
|
|
3257
3280
|
// src/commands/auth.ts
|
|
3258
|
-
function
|
|
3259
|
-
const message = `tarout ${action} requires a browser. Agents should ask the user to run \`tarout token <api-token>\` or set TAROUT_TOKEN \u2014 generate one at https://tarout.sa/dashboard/settings/profile.`;
|
|
3281
|
+
function announceAuthUrl(authUrl, callbackPort, launched) {
|
|
3260
3282
|
if (isJsonMode()) {
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3283
|
+
outputJsonLine({
|
|
3284
|
+
type: "event",
|
|
3285
|
+
event: "auth_url",
|
|
3286
|
+
authUrl,
|
|
3287
|
+
browserLaunched: launched,
|
|
3288
|
+
callbackPort
|
|
3267
3289
|
});
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3290
|
+
return;
|
|
3291
|
+
}
|
|
3292
|
+
if (!canLaunchBrowser()) {
|
|
3293
|
+
log(
|
|
3294
|
+
colors.dim(
|
|
3295
|
+
"On a remote/headless host? Run `tarout token <api-token>` instead \u2014 generate one at https://tarout.sa/dashboard/settings/profile."
|
|
3296
|
+
)
|
|
3297
|
+
);
|
|
3271
3298
|
}
|
|
3272
|
-
exit(ExitCode.AUTH_ERROR);
|
|
3273
3299
|
}
|
|
3274
3300
|
function registerAuthCommands(program2) {
|
|
3275
3301
|
program2.command("login").description("Authenticate with Tarout via browser").option("--api-url <url>", "Custom API URL", "https://tarout.sa").action(async (options) => {
|
|
@@ -3292,21 +3318,16 @@ function registerAuthCommands(program2) {
|
|
|
3292
3318
|
return;
|
|
3293
3319
|
}
|
|
3294
3320
|
}
|
|
3295
|
-
if (isJsonMode() || !canLaunchBrowser()) {
|
|
3296
|
-
refuseBrowserAuthForAgent("login");
|
|
3297
|
-
}
|
|
3298
3321
|
const apiUrl = options.apiUrl;
|
|
3299
3322
|
log("");
|
|
3300
3323
|
log("Opening browser to authenticate...");
|
|
3301
3324
|
const authServer = await startAuthServer();
|
|
3302
3325
|
const callbackUrl = `http://localhost:${authServer.port}/callback?state=${encodeURIComponent(authServer.state)}`;
|
|
3303
3326
|
const authUrl = `${apiUrl}/cli-authorize?callback=${encodeURIComponent(callbackUrl)}`;
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
}
|
|
3307
|
-
|
|
3308
|
-
refuseBrowserAuthForAgent("login");
|
|
3309
|
-
}
|
|
3327
|
+
const launched = await openInBrowser(authUrl, {
|
|
3328
|
+
hint: "If the browser didn't open, visit this URL to authenticate:"
|
|
3329
|
+
});
|
|
3330
|
+
announceAuthUrl(authUrl, authServer.port, launched);
|
|
3310
3331
|
const _spinner = startSpinner("Waiting for authentication...");
|
|
3311
3332
|
try {
|
|
3312
3333
|
const authData = await authServer.waitForCallback();
|
|
@@ -3406,21 +3427,16 @@ function registerAuthCommands(program2) {
|
|
|
3406
3427
|
return;
|
|
3407
3428
|
}
|
|
3408
3429
|
}
|
|
3409
|
-
if (isJsonMode() || !canLaunchBrowser()) {
|
|
3410
|
-
refuseBrowserAuthForAgent("register");
|
|
3411
|
-
}
|
|
3412
3430
|
const apiUrl = options.apiUrl;
|
|
3413
3431
|
log("");
|
|
3414
3432
|
log("Opening browser to create your account...");
|
|
3415
3433
|
const authServer = await startAuthServer();
|
|
3416
3434
|
const callbackUrl = `http://localhost:${authServer.port}/callback?state=${encodeURIComponent(authServer.state)}`;
|
|
3417
3435
|
const authUrl = `${apiUrl}/cli-authorize?action=register&callback=${encodeURIComponent(callbackUrl)}`;
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
}
|
|
3421
|
-
|
|
3422
|
-
refuseBrowserAuthForAgent("register");
|
|
3423
|
-
}
|
|
3436
|
+
const launched = await openInBrowser(authUrl, {
|
|
3437
|
+
hint: "If the browser didn't open, visit this URL to create your account:"
|
|
3438
|
+
});
|
|
3439
|
+
announceAuthUrl(authUrl, authServer.port, launched);
|
|
3424
3440
|
const _spinner = startSpinner("Waiting for account creation...");
|
|
3425
3441
|
try {
|
|
3426
3442
|
const authData = await authServer.waitForCallback();
|
|
@@ -3964,6 +3980,18 @@ import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
|
|
|
3964
3980
|
|
|
3965
3981
|
// src/lib/billing-upgrade.ts
|
|
3966
3982
|
var AGENT_BILLING_PERMISSION_HINT = `If your agent's permission system blocks this billing command, ask the user to allowlist Tarout billing once so you can run it directly (Claude Code: add "Bash(tarout billing:*)" to .claude/settings.json). Running an upgrade only opens the hosted payment page \u2014 the user still completes payment in the browser.`;
|
|
3983
|
+
function storageSlotTierForAddonKey(addonKey) {
|
|
3984
|
+
switch (addonKey) {
|
|
3985
|
+
case "storage.starter":
|
|
3986
|
+
return "STARTER";
|
|
3987
|
+
case "storage.standard":
|
|
3988
|
+
return "STANDARD";
|
|
3989
|
+
case "storage.pro":
|
|
3990
|
+
return "PRO";
|
|
3991
|
+
default:
|
|
3992
|
+
return null;
|
|
3993
|
+
}
|
|
3994
|
+
}
|
|
3967
3995
|
function resolveTarget(input2) {
|
|
3968
3996
|
if (input2.kind === "plan") return input2.planKey ?? "";
|
|
3969
3997
|
if (input2.kind === "addon") {
|
|
@@ -3990,8 +4018,16 @@ async function performBillingChange(client, input2) {
|
|
|
3990
4018
|
addons: input2.addons
|
|
3991
4019
|
});
|
|
3992
4020
|
} else if (kind === "addon") {
|
|
3993
|
-
const
|
|
3994
|
-
|
|
4021
|
+
const storageTier = !input2.addons && input2.addonKey ? storageSlotTierForAddonKey(input2.addonKey) : null;
|
|
4022
|
+
if (storageTier) {
|
|
4023
|
+
result = await client.storage.purchaseStorageSlot.mutate({
|
|
4024
|
+
tier: storageTier,
|
|
4025
|
+
quantity: input2.quantity ?? 1
|
|
4026
|
+
});
|
|
4027
|
+
} else {
|
|
4028
|
+
const items = input2.addons ?? (input2.addonKey ? [{ addonKey: input2.addonKey, quantity: input2.quantity ?? 1 }] : []);
|
|
4029
|
+
result = await client.subscription.purchaseAddons.mutate({ items });
|
|
4030
|
+
}
|
|
3995
4031
|
} else {
|
|
3996
4032
|
result = await client.subscription.setPlanQuantity.mutate({
|
|
3997
4033
|
quantity: input2.quantity
|
|
@@ -4111,7 +4147,7 @@ function emitBillingResult(result, opts) {
|
|
|
4111
4147
|
case "payment_required":
|
|
4112
4148
|
outputData({
|
|
4113
4149
|
...envelope,
|
|
4114
|
-
hint: `Open paymentUrl to complete checkout, then run \`${nextCommand}\` \u2014 or re-run
|
|
4150
|
+
hint: `Open paymentUrl to complete checkout, then run \`${nextCommand}\` \u2014 or re-run without --no-wait (waiting is the default).`
|
|
4115
4151
|
});
|
|
4116
4152
|
box("Payment required", [
|
|
4117
4153
|
`${label}`,
|
|
@@ -4197,6 +4233,16 @@ function isPaidFamily(planKey) {
|
|
|
4197
4233
|
const family = planFamily(planKey);
|
|
4198
4234
|
return family === "SHARED" || family === "DEDICATED";
|
|
4199
4235
|
}
|
|
4236
|
+
function dbAddonKeyForPlanFamily(planKey) {
|
|
4237
|
+
switch (planFamily(planKey)) {
|
|
4238
|
+
case "SHARED":
|
|
4239
|
+
return "db.standard";
|
|
4240
|
+
case "DEDICATED":
|
|
4241
|
+
return "db.pro";
|
|
4242
|
+
default:
|
|
4243
|
+
return null;
|
|
4244
|
+
}
|
|
4245
|
+
}
|
|
4200
4246
|
function resourceAddonKeysForPlan(planKey) {
|
|
4201
4247
|
switch (planFamily(planKey)) {
|
|
4202
4248
|
case "SHARED":
|
|
@@ -4230,6 +4276,23 @@ function isConflictError(err) {
|
|
|
4230
4276
|
const e = err;
|
|
4231
4277
|
return (e?.code ?? e?.data?.code) === "CONFLICT";
|
|
4232
4278
|
}
|
|
4279
|
+
async function previewAddonAmountDue(client, addonKey, quantity) {
|
|
4280
|
+
try {
|
|
4281
|
+
if (storageSlotTierForAddonKey(addonKey)) {
|
|
4282
|
+
const catalog = await client.subscription.getCatalog.query();
|
|
4283
|
+
const addon = (catalog?.addons ?? []).find(
|
|
4284
|
+
(a) => (a.key ?? a.addonKey) === addonKey
|
|
4285
|
+
);
|
|
4286
|
+
return typeof addon?.priceHalalas === "number" ? addon.priceHalalas * quantity : void 0;
|
|
4287
|
+
}
|
|
4288
|
+
const preview = await client.subscription.previewAddonsPurchase.query({
|
|
4289
|
+
items: [{ addonKey, quantity }]
|
|
4290
|
+
});
|
|
4291
|
+
return typeof preview?.totalProratedHalalas === "number" ? preview.totalProratedHalalas : void 0;
|
|
4292
|
+
} catch {
|
|
4293
|
+
return void 0;
|
|
4294
|
+
}
|
|
4295
|
+
}
|
|
4233
4296
|
function registerBillingCommands(program2) {
|
|
4234
4297
|
const billing = program2.command("billing").description("Manage subscription and billing");
|
|
4235
4298
|
billing.command("status").description("Show current subscription and entitlements").action(async () => {
|
|
@@ -4328,8 +4391,8 @@ function registerBillingCommands(program2) {
|
|
|
4328
4391
|
collectAddon,
|
|
4329
4392
|
[]
|
|
4330
4393
|
).option(
|
|
4331
|
-
"-
|
|
4332
|
-
"
|
|
4394
|
+
"--no-wait",
|
|
4395
|
+
"Return as soon as the hosted checkout opens, without polling for confirmation (default: wait until paid)"
|
|
4333
4396
|
).option(
|
|
4334
4397
|
"--timeout <seconds>",
|
|
4335
4398
|
"Maximum wait time in seconds (default 600)",
|
|
@@ -4606,8 +4669,8 @@ function registerBillingCommands(program2) {
|
|
|
4606
4669
|
}
|
|
4607
4670
|
});
|
|
4608
4671
|
billing.command("addon:add").argument("<addon>", "Addon key to add").option("-q, --quantity <n>", "Addon quantity", Number.parseInt).option(
|
|
4609
|
-
"-
|
|
4610
|
-
"
|
|
4672
|
+
"--no-wait",
|
|
4673
|
+
"Return as soon as the hosted checkout opens, without polling for confirmation (default: wait until paid)"
|
|
4611
4674
|
).option(
|
|
4612
4675
|
"--timeout <seconds>",
|
|
4613
4676
|
"Maximum wait time in seconds (default 600)",
|
|
@@ -4617,26 +4680,38 @@ function registerBillingCommands(program2) {
|
|
|
4617
4680
|
try {
|
|
4618
4681
|
if (!isLoggedIn()) throw new AuthError();
|
|
4619
4682
|
const quantity = options.quantity || 1;
|
|
4683
|
+
const client = getApiClient();
|
|
4620
4684
|
if (!shouldSkipConfirmation()) {
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
const confirmed = await confirm(
|
|
4626
|
-
`Add addon "${addonKey}" \xD7 ${quantity}?`,
|
|
4627
|
-
false,
|
|
4628
|
-
{
|
|
4629
|
-
field: "confirm_addon_add",
|
|
4630
|
-
flag: "--yes",
|
|
4631
|
-
context: { addonKey, quantity }
|
|
4632
|
-
}
|
|
4685
|
+
const amountDueHalalas = await previewAddonAmountDue(
|
|
4686
|
+
client,
|
|
4687
|
+
addonKey,
|
|
4688
|
+
quantity
|
|
4633
4689
|
);
|
|
4634
|
-
if (
|
|
4635
|
-
log(
|
|
4636
|
-
|
|
4690
|
+
if (shouldAutoConfirmPaidCheckout(amountDueHalalas)) {
|
|
4691
|
+
log(
|
|
4692
|
+
`
|
|
4693
|
+
Adding ${colors.cyan(addonKey)} \xD7 ${quantity} \u2014 opening the secure payment page...`
|
|
4694
|
+
);
|
|
4695
|
+
} else {
|
|
4696
|
+
log("");
|
|
4697
|
+
log(`Addon: ${colors.cyan(addonKey)}`);
|
|
4698
|
+
log(`Quantity: ${quantity}`);
|
|
4699
|
+
log("");
|
|
4700
|
+
const confirmed = await confirm(
|
|
4701
|
+
`Add addon "${addonKey}" \xD7 ${quantity}?`,
|
|
4702
|
+
false,
|
|
4703
|
+
{
|
|
4704
|
+
field: "confirm_addon_add",
|
|
4705
|
+
flag: "--yes",
|
|
4706
|
+
context: { addonKey, quantity, amountDueHalalas }
|
|
4707
|
+
}
|
|
4708
|
+
);
|
|
4709
|
+
if (!confirmed) {
|
|
4710
|
+
log("Cancelled.");
|
|
4711
|
+
return;
|
|
4712
|
+
}
|
|
4637
4713
|
}
|
|
4638
4714
|
}
|
|
4639
|
-
const client = getApiClient();
|
|
4640
4715
|
const _spinner = startSpinner("Adding addon...");
|
|
4641
4716
|
let raw;
|
|
4642
4717
|
try {
|
|
@@ -4707,8 +4782,8 @@ function registerBillingCommands(program2) {
|
|
|
4707
4782
|
}
|
|
4708
4783
|
});
|
|
4709
4784
|
billing.command("plan:quantity").argument("<quantity>", "New plan quantity", Number.parseInt).option(
|
|
4710
|
-
"-
|
|
4711
|
-
"
|
|
4785
|
+
"--no-wait",
|
|
4786
|
+
"Return as soon as the hosted checkout opens, without polling for confirmation (default: wait until paid)"
|
|
4712
4787
|
).option(
|
|
4713
4788
|
"--timeout <seconds>",
|
|
4714
4789
|
"Maximum wait time in seconds (default 600)",
|
|
@@ -4781,8 +4856,8 @@ function registerBillingCommands(program2) {
|
|
|
4781
4856
|
}
|
|
4782
4857
|
});
|
|
4783
4858
|
billing.command("addon:buy").argument("<addon>", "Addon key").option("-q, --quantity <n>", "Quantity", Number.parseInt).option(
|
|
4784
|
-
"-
|
|
4785
|
-
"
|
|
4859
|
+
"--no-wait",
|
|
4860
|
+
"Return as soon as the hosted checkout opens, without polling for confirmation (default: wait until paid)"
|
|
4786
4861
|
).option(
|
|
4787
4862
|
"--timeout <seconds>",
|
|
4788
4863
|
"Maximum wait time in seconds (default 600)",
|
|
@@ -4792,20 +4867,32 @@ function registerBillingCommands(program2) {
|
|
|
4792
4867
|
try {
|
|
4793
4868
|
if (!isLoggedIn()) throw new AuthError();
|
|
4794
4869
|
const quantity = options.quantity || 1;
|
|
4870
|
+
const client = getApiClient();
|
|
4795
4871
|
if (!shouldSkipConfirmation()) {
|
|
4796
|
-
|
|
4872
|
+
const amountDueHalalas = await previewAddonAmountDue(
|
|
4873
|
+
client,
|
|
4874
|
+
addonKey,
|
|
4875
|
+
quantity
|
|
4876
|
+
);
|
|
4877
|
+
if (shouldAutoConfirmPaidCheckout(amountDueHalalas)) {
|
|
4878
|
+
log(
|
|
4879
|
+
`
|
|
4880
|
+
Purchasing ${quantity}\xD7 ${colors.cyan(addonKey)} \u2014 opening the secure payment page...`
|
|
4881
|
+
);
|
|
4882
|
+
} else {
|
|
4883
|
+
log(`
|
|
4797
4884
|
Purchase ${quantity}\xD7 ${colors.cyan(addonKey)}?`);
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4885
|
+
const confirmed = await confirm("Proceed?", false, {
|
|
4886
|
+
field: "confirm_addon_buy",
|
|
4887
|
+
flag: "--yes",
|
|
4888
|
+
context: { addonKey, quantity, amountDueHalalas }
|
|
4889
|
+
});
|
|
4890
|
+
if (!confirmed) {
|
|
4891
|
+
log("Cancelled.");
|
|
4892
|
+
return;
|
|
4893
|
+
}
|
|
4806
4894
|
}
|
|
4807
4895
|
}
|
|
4808
|
-
const client = getApiClient();
|
|
4809
4896
|
const _spinner = startSpinner("Purchasing addon...");
|
|
4810
4897
|
const result = await performBillingChange(client, {
|
|
4811
4898
|
kind: "addon",
|
|
@@ -5735,7 +5822,7 @@ import {
|
|
|
5735
5822
|
import { tmpdir } from "os";
|
|
5736
5823
|
import { basename, dirname, join as join4 } from "path";
|
|
5737
5824
|
import { promisify } from "util";
|
|
5738
|
-
import
|
|
5825
|
+
import open3 from "open";
|
|
5739
5826
|
|
|
5740
5827
|
// src/lib/agent-setup.ts
|
|
5741
5828
|
var SETUP_HINT = "Run `tarout agent init` to allowlist Bash(tarout:*) so tarout commands run without per-command approval prompts.";
|
|
@@ -5782,6 +5869,21 @@ function nextPlanForRequested(requested) {
|
|
|
5782
5869
|
if (r === "dedicated_large") return "dedicated_large";
|
|
5783
5870
|
return r;
|
|
5784
5871
|
}
|
|
5872
|
+
function nextPlanForCurrent(currentPlanKey) {
|
|
5873
|
+
const family = planFamily(currentPlanKey);
|
|
5874
|
+
if (family === "SHARED") return "dedicated_small";
|
|
5875
|
+
if (family === "DEDICATED") {
|
|
5876
|
+
const k = (currentPlanKey ?? "").trim().toLowerCase();
|
|
5877
|
+
if (k === "dedicated_large") return "dedicated_large";
|
|
5878
|
+
if (k === "dedicated_medium") return "dedicated_large";
|
|
5879
|
+
return "dedicated_medium";
|
|
5880
|
+
}
|
|
5881
|
+
return "shared";
|
|
5882
|
+
}
|
|
5883
|
+
function upgradeTargetPlan(opts) {
|
|
5884
|
+
if (opts?.currentPlanKey) return nextPlanForCurrent(opts.currentPlanKey);
|
|
5885
|
+
return nextPlanForRequested(opts?.requestedPlan);
|
|
5886
|
+
}
|
|
5785
5887
|
function resolveEntitlementRemedy(failedKey, catalog, opts) {
|
|
5786
5888
|
const plans = catalog?.plans ?? [];
|
|
5787
5889
|
const addons = catalog?.addons ?? [];
|
|
@@ -5801,7 +5903,7 @@ function resolveEntitlementRemedy(failedKey, catalog, opts) {
|
|
|
5801
5903
|
}
|
|
5802
5904
|
if (failedKey?.startsWith("app.dedicated") || failedKey?.startsWith("host.")) {
|
|
5803
5905
|
const requested = opts?.requestedPlan;
|
|
5804
|
-
const target2 = requested && requested.toLowerCase().startsWith("dedicated") ? nextPlanForRequested(requested) : "dedicated_small";
|
|
5906
|
+
const target2 = planFamily(opts?.currentPlanKey) === "DEDICATED" ? nextPlanForCurrent(opts?.currentPlanKey) : requested && requested.toLowerCase().startsWith("dedicated") ? nextPlanForRequested(requested) : "dedicated_small";
|
|
5805
5907
|
const plan2 = plans.find((p) => planKeyOf(p) === target2);
|
|
5806
5908
|
return {
|
|
5807
5909
|
kind: "plan",
|
|
@@ -5814,7 +5916,7 @@ function resolveEntitlementRemedy(failedKey, catalog, opts) {
|
|
|
5814
5916
|
};
|
|
5815
5917
|
}
|
|
5816
5918
|
if (failedKey === "db.free.slots" || failedKey === "storage.free.slots") {
|
|
5817
|
-
const target2 =
|
|
5919
|
+
const target2 = upgradeTargetPlan(opts);
|
|
5818
5920
|
const plan2 = plans.find((p) => planKeyOf(p) === target2);
|
|
5819
5921
|
const resource = failedKey === "db.free.slots" ? "database" : "storage bucket";
|
|
5820
5922
|
return {
|
|
@@ -5842,7 +5944,7 @@ function resolveEntitlementRemedy(failedKey, catalog, opts) {
|
|
|
5842
5944
|
hint: `Add a ${matched?.name ?? targetKey} slot with an addon purchase.`
|
|
5843
5945
|
};
|
|
5844
5946
|
}
|
|
5845
|
-
const target =
|
|
5947
|
+
const target = upgradeTargetPlan(opts);
|
|
5846
5948
|
const plan = plans.find((p) => planKeyOf(p) === target);
|
|
5847
5949
|
return {
|
|
5848
5950
|
kind: "plan",
|
|
@@ -6018,7 +6120,9 @@ async function authenticateViaBrowser(action, apiUrl) {
|
|
|
6018
6120
|
log(
|
|
6019
6121
|
action === "register" ? "Opening browser to create your account..." : "Opening browser to authenticate..."
|
|
6020
6122
|
);
|
|
6021
|
-
await
|
|
6123
|
+
await openInBrowser(authUrl, {
|
|
6124
|
+
hint: action === "register" ? "If the browser didn't open, visit this URL to create your account:" : "If the browser didn't open, visit this URL to authenticate:"
|
|
6125
|
+
});
|
|
6022
6126
|
const _spinner = startSpinner(
|
|
6023
6127
|
action === "register" ? "Waiting for account creation..." : "Waiting for authentication..."
|
|
6024
6128
|
);
|
|
@@ -6705,6 +6809,14 @@ async function getCurrentPlanQuantitySafely(client) {
|
|
|
6705
6809
|
return 1;
|
|
6706
6810
|
}
|
|
6707
6811
|
}
|
|
6812
|
+
async function getCurrentPlanKeySafely(client) {
|
|
6813
|
+
try {
|
|
6814
|
+
const sub = await client.subscription.getCurrent.query();
|
|
6815
|
+
return sub?.planKey ?? "free";
|
|
6816
|
+
} catch {
|
|
6817
|
+
return void 0;
|
|
6818
|
+
}
|
|
6819
|
+
}
|
|
6708
6820
|
async function runInlineTargetedRemedy(client, remedy) {
|
|
6709
6821
|
let input2;
|
|
6710
6822
|
let label;
|
|
@@ -6748,13 +6860,29 @@ async function runInlineTargetedRemedy(client, remedy) {
|
|
|
6748
6860
|
}
|
|
6749
6861
|
async function promptEntitlementRemedy(client, err, requestedPlan) {
|
|
6750
6862
|
const failedKey = extractEntitlementKeyFromError(err);
|
|
6751
|
-
const catalog = await
|
|
6752
|
-
|
|
6863
|
+
const [catalog, currentPlanKey] = await Promise.all([
|
|
6864
|
+
fetchCatalogSafely(client),
|
|
6865
|
+
getCurrentPlanKeySafely(client)
|
|
6866
|
+
]);
|
|
6867
|
+
const remedy = resolveEntitlementRemedy(failedKey, catalog, {
|
|
6868
|
+
requestedPlan,
|
|
6869
|
+
currentPlanKey
|
|
6870
|
+
});
|
|
6753
6871
|
if (remedy.kind === "plan") {
|
|
6754
|
-
return promptUpgradeFromEntitlementError(
|
|
6872
|
+
return promptUpgradeFromEntitlementError(
|
|
6873
|
+
client,
|
|
6874
|
+
err,
|
|
6875
|
+
requestedPlan,
|
|
6876
|
+
currentPlanKey
|
|
6877
|
+
);
|
|
6755
6878
|
}
|
|
6756
6879
|
const price = remedy.priceHalalas !== void 0 ? ` (${formatPlanPrice(remedy.priceHalalas)})` : "";
|
|
6757
6880
|
const targetedLabel = remedy.kind === "plan_quantity" ? `Add one more app slot${price}` : `Add just the ${remedy.targetName ?? remedy.targetKey}${price}`;
|
|
6881
|
+
const upgradePlanKey = upgradeTargetPlan({ currentPlanKey, requestedPlan });
|
|
6882
|
+
const upgradePlanDef = (catalog?.plans ?? []).find(
|
|
6883
|
+
(p) => (p.planKey ?? p.key) === upgradePlanKey
|
|
6884
|
+
);
|
|
6885
|
+
const upgradeLabel = `Upgrade to ${upgradePlanDef?.name ?? upgradePlanKey}`;
|
|
6758
6886
|
log("");
|
|
6759
6887
|
log(colors.warn("That resource isn't included in your current plan."));
|
|
6760
6888
|
log("");
|
|
@@ -6764,7 +6892,7 @@ async function promptEntitlementRemedy(client, err, requestedPlan) {
|
|
|
6764
6892
|
const choice = await select(
|
|
6765
6893
|
"How would you like to add it?",
|
|
6766
6894
|
[
|
|
6767
|
-
{ name:
|
|
6895
|
+
{ name: upgradeLabel, value: UPGRADE },
|
|
6768
6896
|
{ name: targetedLabel, value: TARGETED },
|
|
6769
6897
|
{ name: "Cancel", value: CANCEL }
|
|
6770
6898
|
],
|
|
@@ -6775,13 +6903,19 @@ async function promptEntitlementRemedy(client, err, requestedPlan) {
|
|
|
6775
6903
|
failedEntitlementKey: failedKey,
|
|
6776
6904
|
remedyKind: remedy.kind,
|
|
6777
6905
|
targetKey: remedy.targetKey,
|
|
6778
|
-
command: remedy.command
|
|
6906
|
+
command: remedy.command,
|
|
6907
|
+
upgradePlanKey
|
|
6779
6908
|
}
|
|
6780
6909
|
}
|
|
6781
6910
|
);
|
|
6782
6911
|
if (choice === CANCEL) return false;
|
|
6783
6912
|
if (choice === UPGRADE) {
|
|
6784
|
-
return promptUpgradeFromEntitlementError(
|
|
6913
|
+
return promptUpgradeFromEntitlementError(
|
|
6914
|
+
client,
|
|
6915
|
+
err,
|
|
6916
|
+
requestedPlan,
|
|
6917
|
+
currentPlanKey
|
|
6918
|
+
);
|
|
6785
6919
|
}
|
|
6786
6920
|
return runInlineTargetedRemedy(client, remedy);
|
|
6787
6921
|
}
|
|
@@ -6875,9 +7009,9 @@ function extractEntitlementKeyFromError(err) {
|
|
|
6875
7009
|
const m = msg.match(/Plan limit reached for ([\w.]+)/i);
|
|
6876
7010
|
return m?.[1];
|
|
6877
7011
|
}
|
|
6878
|
-
function buildRemedyOptions(remedy, requestedPlan, catalog) {
|
|
7012
|
+
function buildRemedyOptions(remedy, requestedPlan, catalog, currentPlanKey) {
|
|
6879
7013
|
if (remedy.kind === "addon" || remedy.kind === "plan_quantity") {
|
|
6880
|
-
const upgradePlan =
|
|
7014
|
+
const upgradePlan = upgradeTargetPlan({ currentPlanKey, requestedPlan });
|
|
6881
7015
|
const planDef = (catalog?.plans ?? []).find(
|
|
6882
7016
|
(p) => (p.planKey ?? p.key) === upgradePlan
|
|
6883
7017
|
);
|
|
@@ -6905,10 +7039,21 @@ function buildRemedyOptions(remedy, requestedPlan, catalog) {
|
|
|
6905
7039
|
async function emitNeedsUpgrade(client, err, requestedPlan, retryCommand) {
|
|
6906
7040
|
const message = err instanceof Error ? err.message : "Plan upgrade required";
|
|
6907
7041
|
const failedKey = extractEntitlementKeyFromError(err);
|
|
6908
|
-
const catalog = await
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
7042
|
+
const [catalog, currentPlanKey] = await Promise.all([
|
|
7043
|
+
fetchCatalogSafely(client),
|
|
7044
|
+
getCurrentPlanKeySafely(client)
|
|
7045
|
+
]);
|
|
7046
|
+
const remedy = resolveEntitlementRemedy(failedKey, catalog, {
|
|
7047
|
+
requestedPlan,
|
|
7048
|
+
currentPlanKey
|
|
7049
|
+
});
|
|
7050
|
+
const options = buildRemedyOptions(
|
|
7051
|
+
remedy,
|
|
7052
|
+
requestedPlan,
|
|
7053
|
+
catalog,
|
|
7054
|
+
currentPlanKey
|
|
7055
|
+
);
|
|
7056
|
+
const hint = options.length > 1 ? `Two ways to resolve this \u2014 ask the user which they prefer, do not choose for them: (1) ${options[0]?.label}: \`${options[0]?.command}\`; (2) ${options[1]?.label}: \`${options[1]?.command}\`. The chosen command opens the payment page and waits until it's confirmed, then retry: ${retryCommand}.` : `${remedy.hint} That command opens the payment page and waits until it's confirmed, then retry: ${retryCommand}.`;
|
|
6912
7057
|
outputError("NEEDS_UPGRADE", message, {
|
|
6913
7058
|
failedEntitlementKey: failedKey,
|
|
6914
7059
|
remedyKind: remedy.kind,
|
|
@@ -6979,7 +7124,7 @@ async function explainFreeResourceSlotExhaustion(client, failedKey) {
|
|
|
6979
7124
|
log(` \u2022 Upgrade: ${colors.cyan("tarout billing upgrade shared --wait")}`);
|
|
6980
7125
|
log("");
|
|
6981
7126
|
}
|
|
6982
|
-
async function promptUpgradeFromEntitlementError(client, err, requestedPlan) {
|
|
7127
|
+
async function promptUpgradeFromEntitlementError(client, err, requestedPlan, currentPlanKey) {
|
|
6983
7128
|
const catalog = await fetchCatalogSafely(client);
|
|
6984
7129
|
const allPlans = catalog?.plans ?? [];
|
|
6985
7130
|
const upgradeable = allPlans.filter(
|
|
@@ -6989,10 +7134,14 @@ async function promptUpgradeFromEntitlementError(client, err, requestedPlan) {
|
|
|
6989
7134
|
return false;
|
|
6990
7135
|
}
|
|
6991
7136
|
const failedKey = extractEntitlementKeyFromError(err);
|
|
7137
|
+
const ladderKey = upgradeTargetPlan({ currentPlanKey, requestedPlan });
|
|
7138
|
+
const ladderPlan = upgradeable.find(
|
|
7139
|
+
(p) => (p.planKey || p.key) === ladderKey
|
|
7140
|
+
);
|
|
6992
7141
|
const matchingPlan = failedKey ? upgradeable.find(
|
|
6993
7142
|
(p) => p.grants?.some((g) => g.entitlementKey === failedKey)
|
|
6994
7143
|
) : void 0;
|
|
6995
|
-
const recommendedKey = matchingPlan ? matchingPlan.planKey || matchingPlan.key : inferSuggestedPlan(requestedPlan);
|
|
7144
|
+
const recommendedKey = ladderPlan ? ladderKey : matchingPlan ? matchingPlan.planKey || matchingPlan.key : inferSuggestedPlan(requestedPlan);
|
|
6996
7145
|
log("");
|
|
6997
7146
|
log(colors.bold("Available upgrade plans:"));
|
|
6998
7147
|
log("");
|
|
@@ -7122,7 +7271,7 @@ async function openGitProviderSetup() {
|
|
|
7122
7271
|
log(
|
|
7123
7272
|
`No GitHub account is required if you keep using ${colors.dim("--source upload")}.`
|
|
7124
7273
|
);
|
|
7125
|
-
await
|
|
7274
|
+
await open3(url);
|
|
7126
7275
|
}
|
|
7127
7276
|
async function configureOptionalResources(client, profile, app, options, inspection) {
|
|
7128
7277
|
const database = await resolveDatabaseChoice(options, inspection);
|
|
@@ -7275,6 +7424,57 @@ async function resolveResourcePlan(client, kind, value, message) {
|
|
|
7275
7424
|
}
|
|
7276
7425
|
});
|
|
7277
7426
|
}
|
|
7427
|
+
function dbTierForAddonKey(addonKey) {
|
|
7428
|
+
if (addonKey === "db.pro") return "PRO";
|
|
7429
|
+
if (addonKey === "db.standard") return "STANDARD";
|
|
7430
|
+
return "STARTER";
|
|
7431
|
+
}
|
|
7432
|
+
async function ensureDatabasePlan(client, requested) {
|
|
7433
|
+
const currentPlanKey = await getCurrentPlanKeySafely(client);
|
|
7434
|
+
const addonKey = dbAddonKeyForPlanFamily(currentPlanKey);
|
|
7435
|
+
const orgIsPaid = addonKey !== null;
|
|
7436
|
+
if (requested && !(requested === "FREE" && orgIsPaid)) {
|
|
7437
|
+
return { ok: true, plan: requested };
|
|
7438
|
+
}
|
|
7439
|
+
const tiers = await loadResourceTiers(client, "database");
|
|
7440
|
+
const hasCreatable = tiers.some((t) => t.canCreate);
|
|
7441
|
+
if (hasCreatable || !orgIsPaid) {
|
|
7442
|
+
return { ok: true, plan: pickDefaultResourceTier(tiers) };
|
|
7443
|
+
}
|
|
7444
|
+
if (!isJsonMode()) {
|
|
7445
|
+
log("");
|
|
7446
|
+
log(
|
|
7447
|
+
colors.dim(
|
|
7448
|
+
`Your plan has no open database slot \u2014 adding the ${addonKey} add-on. Complete payment in the browser to continue.`
|
|
7449
|
+
)
|
|
7450
|
+
);
|
|
7451
|
+
}
|
|
7452
|
+
const result = await performBillingChange(client, {
|
|
7453
|
+
kind: "addon",
|
|
7454
|
+
addonKey,
|
|
7455
|
+
quantity: 1,
|
|
7456
|
+
wait: true,
|
|
7457
|
+
timeoutMs: 6e5,
|
|
7458
|
+
openBrowser: paymentBrowserOpener(),
|
|
7459
|
+
onCheckoutOpened: ({ orderId, paymentUrl }) => {
|
|
7460
|
+
if (!isJsonMode()) {
|
|
7461
|
+
log("");
|
|
7462
|
+
log("Open this URL to complete payment:");
|
|
7463
|
+
log(` ${colors.cyan(paymentUrl)}`);
|
|
7464
|
+
log(`Order ID: ${colors.dim(orderId)}`);
|
|
7465
|
+
}
|
|
7466
|
+
}
|
|
7467
|
+
});
|
|
7468
|
+
if (result.status === "applied" || result.status === "paid") {
|
|
7469
|
+
return { ok: true, plan: dbTierForAddonKey(addonKey) };
|
|
7470
|
+
}
|
|
7471
|
+
return { ok: false, result };
|
|
7472
|
+
}
|
|
7473
|
+
async function resolveDatabasePlanOrExit(client, requested) {
|
|
7474
|
+
const resolution = await ensureDatabasePlan(client, requested);
|
|
7475
|
+
if (resolution.ok) return resolution.plan;
|
|
7476
|
+
exit(emitBillingResult(resolution.result, { label: "Database add-on" }));
|
|
7477
|
+
}
|
|
7278
7478
|
async function createAndAttachDatabase(client, profile, app, input2) {
|
|
7279
7479
|
const appName = generateResourceSlug(app.name, input2.kind);
|
|
7280
7480
|
const label = input2.kind === "postgres" ? "Postgres" : "MySQL";
|
|
@@ -7412,12 +7612,7 @@ async function resolveDatabaseProvisioning(client, profile, app, kind, options)
|
|
|
7412
7612
|
}
|
|
7413
7613
|
);
|
|
7414
7614
|
if (picked === "__create__") {
|
|
7415
|
-
const plan =
|
|
7416
|
-
client,
|
|
7417
|
-
"database",
|
|
7418
|
-
void 0,
|
|
7419
|
-
"Database plan:"
|
|
7420
|
-
);
|
|
7615
|
+
const plan = await resolveDatabasePlanOrExit(client, requestedPlan);
|
|
7421
7616
|
return {
|
|
7422
7617
|
action: "create",
|
|
7423
7618
|
plan,
|
|
@@ -7782,7 +7977,7 @@ async function attachExistingStorage(client, app, decision) {
|
|
|
7782
7977
|
}
|
|
7783
7978
|
}
|
|
7784
7979
|
async function buildDatabaseCreateDecision(client, app, kind, requestedPlan) {
|
|
7785
|
-
const plan =
|
|
7980
|
+
const plan = await resolveDatabasePlanOrExit(client, requestedPlan);
|
|
7786
7981
|
return {
|
|
7787
7982
|
action: "create",
|
|
7788
7983
|
plan,
|
|
@@ -8879,10 +9074,7 @@ function normalizeDbPlan(value) {
|
|
|
8879
9074
|
);
|
|
8880
9075
|
}
|
|
8881
9076
|
async function resolveDbPlan(client, explicit) {
|
|
8882
|
-
|
|
8883
|
-
if (normalized) return normalized;
|
|
8884
|
-
const tiers = await loadResourceTiers(client, "database");
|
|
8885
|
-
return pickDefaultResourceTier(tiers);
|
|
9077
|
+
return resolveDatabasePlanOrExit(client, normalizeDbPlan(explicit));
|
|
8886
9078
|
}
|
|
8887
9079
|
function registerDbCommands(program2) {
|
|
8888
9080
|
const db = program2.command("db").description("Manage databases");
|
|
@@ -9033,6 +9225,39 @@ function registerDbCommands(program2) {
|
|
|
9033
9225
|
}
|
|
9034
9226
|
log("");
|
|
9035
9227
|
} catch (err) {
|
|
9228
|
+
if (isEntitlementError(err)) {
|
|
9229
|
+
if (isJsonMode() || isNonInteractiveMode() || shouldSkipConfirmation()) {
|
|
9230
|
+
await emitNeedsUpgrade(
|
|
9231
|
+
getApiClient(),
|
|
9232
|
+
err,
|
|
9233
|
+
void 0,
|
|
9234
|
+
"tarout db create"
|
|
9235
|
+
);
|
|
9236
|
+
exit(ExitCode.PERMISSION_DENIED);
|
|
9237
|
+
}
|
|
9238
|
+
const message = err instanceof Error ? err.message : "Plan upgrade required";
|
|
9239
|
+
log("");
|
|
9240
|
+
log(colors.warn(message));
|
|
9241
|
+
const upgraded = await promptEntitlementRemedy(
|
|
9242
|
+
getApiClient(),
|
|
9243
|
+
err,
|
|
9244
|
+
void 0
|
|
9245
|
+
);
|
|
9246
|
+
if (!upgraded) {
|
|
9247
|
+
await emitNeedsUpgrade(
|
|
9248
|
+
getApiClient(),
|
|
9249
|
+
err,
|
|
9250
|
+
void 0,
|
|
9251
|
+
"tarout db create"
|
|
9252
|
+
);
|
|
9253
|
+
exit(ExitCode.PERMISSION_DENIED);
|
|
9254
|
+
}
|
|
9255
|
+
box("Billing updated", [
|
|
9256
|
+
colors.success("Subscription updated."),
|
|
9257
|
+
`Run ${colors.cyan("tarout db create")} again to create the database.`
|
|
9258
|
+
]);
|
|
9259
|
+
return;
|
|
9260
|
+
}
|
|
9036
9261
|
handleError(err);
|
|
9037
9262
|
}
|
|
9038
9263
|
});
|
|
@@ -13221,8 +13446,7 @@ function registerInitCommand(program2) {
|
|
|
13221
13446
|
"Custom API URL (defaults to saved profile or https://tarout.sa)"
|
|
13222
13447
|
).option("--token <token>", "API token for this run").option("--name <name>", "Application name (defaults to directory name)").option(
|
|
13223
13448
|
"--plan <plan>",
|
|
13224
|
-
"App hosting plan: free, shared, or dedicated"
|
|
13225
|
-
"free"
|
|
13449
|
+
"App hosting plan: free, shared, or dedicated (defaults to your org's subscribed tier)"
|
|
13226
13450
|
).option("-r, --region <region>", "Deployment region", DEFAULT_REGION2).option("--description <text>", "Description for the newly created app").option(
|
|
13227
13451
|
"--database <type>",
|
|
13228
13452
|
"Provision a database: none, postgres, or mysql (defaults to auto-detected)"
|
|
@@ -13291,13 +13515,12 @@ function registerInitCommand(program2) {
|
|
|
13291
13515
|
});
|
|
13292
13516
|
} catch (err) {
|
|
13293
13517
|
if (isEntitlementError(err)) {
|
|
13294
|
-
|
|
13295
|
-
|
|
13296
|
-
|
|
13297
|
-
|
|
13298
|
-
|
|
13299
|
-
|
|
13300
|
-
});
|
|
13518
|
+
await emitNeedsUpgrade(
|
|
13519
|
+
getApiClient(),
|
|
13520
|
+
err,
|
|
13521
|
+
options.plan,
|
|
13522
|
+
"tarout init"
|
|
13523
|
+
);
|
|
13301
13524
|
exit(ExitCode.PERMISSION_DENIED);
|
|
13302
13525
|
}
|
|
13303
13526
|
throw err;
|
|
@@ -15527,7 +15750,7 @@ function registerProjectsCommands(program2) {
|
|
|
15527
15750
|
}
|
|
15528
15751
|
|
|
15529
15752
|
// src/commands/providers.ts
|
|
15530
|
-
import
|
|
15753
|
+
import open4 from "open";
|
|
15531
15754
|
function registerProvidersCommands(program2) {
|
|
15532
15755
|
const providers = program2.command("providers").description("Manage Git providers (GitHub, GitLab, Bitbucket)");
|
|
15533
15756
|
providers.command("list").alias("ls").description("List all connected Git providers").action(async () => {
|
|
@@ -15644,7 +15867,7 @@ function registerProvidersCommands(program2) {
|
|
|
15644
15867
|
log(
|
|
15645
15868
|
"Complete the GitHub browser flow, then connect the repository to your app."
|
|
15646
15869
|
);
|
|
15647
|
-
await
|
|
15870
|
+
await open4(url);
|
|
15648
15871
|
} catch (err) {
|
|
15649
15872
|
handleError(err);
|
|
15650
15873
|
}
|
|
@@ -20112,11 +20335,16 @@ function registerWalletCommands(program2) {
|
|
|
20112
20335
|
outputData(result);
|
|
20113
20336
|
return;
|
|
20114
20337
|
}
|
|
20338
|
+
const paymentUrl = result.paymentUrl || result.url || "";
|
|
20115
20339
|
box("Wallet Top-Up", [
|
|
20116
20340
|
`Order ID: ${colors.cyan(result.orderId || "")}`,
|
|
20117
20341
|
`Amount: ${result.amount ? `${(result.amount / 100).toFixed(2)} SAR` : "Default"}`,
|
|
20118
|
-
`Payment URL: ${colors.cyan(
|
|
20342
|
+
`Payment URL: ${colors.cyan(paymentUrl)}`
|
|
20119
20343
|
]);
|
|
20344
|
+
if (paymentUrl) {
|
|
20345
|
+
const openPayment = paymentBrowserOpener();
|
|
20346
|
+
if (openPayment) await openPayment(paymentUrl);
|
|
20347
|
+
}
|
|
20120
20348
|
log("Complete payment in your browser to credit the wallet.");
|
|
20121
20349
|
log(
|
|
20122
20350
|
`Confirm after payment: ${colors.dim(`tarout wallet confirm ${result.orderId || "<orderId>"}`)}`
|