@tarout/cli 0.10.0 → 0.10.1
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 +246 -73
- 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.10.
|
|
65
|
+
version: "0.10.1",
|
|
66
66
|
description: "Tarout CLI \u2014 the Saudi cloud platform for coding agents",
|
|
67
67
|
type: "module",
|
|
68
68
|
bin: {
|
|
@@ -3079,9 +3079,6 @@ function formatTime(ts) {
|
|
|
3079
3079
|
});
|
|
3080
3080
|
}
|
|
3081
3081
|
|
|
3082
|
-
// src/commands/auth.ts
|
|
3083
|
-
import open3 from "open";
|
|
3084
|
-
|
|
3085
3082
|
// src/lib/auth-server.ts
|
|
3086
3083
|
import { randomBytes, timingSafeEqual } from "crypto";
|
|
3087
3084
|
import express from "express";
|
|
@@ -3240,12 +3237,34 @@ function canLaunchBrowser() {
|
|
|
3240
3237
|
}
|
|
3241
3238
|
return Boolean(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
|
|
3242
3239
|
}
|
|
3240
|
+
async function openInBrowser(url, opts) {
|
|
3241
|
+
if (!isJsonMode()) {
|
|
3242
|
+
log(
|
|
3243
|
+
colors.dim(opts?.hint ?? "If the browser didn't open, visit this URL:")
|
|
3244
|
+
);
|
|
3245
|
+
log(` ${colors.cyan(url)}`);
|
|
3246
|
+
}
|
|
3247
|
+
if (opts?.noOpen || !canLaunchBrowser()) return false;
|
|
3248
|
+
try {
|
|
3249
|
+
await open2(url);
|
|
3250
|
+
return true;
|
|
3251
|
+
} catch {
|
|
3252
|
+
return false;
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3243
3255
|
function paymentBrowserOpener(opts) {
|
|
3244
3256
|
if (opts?.noOpen || !canLaunchBrowser()) return void 0;
|
|
3245
3257
|
return async (url) => {
|
|
3246
3258
|
try {
|
|
3247
3259
|
await open2(url);
|
|
3248
3260
|
} catch {
|
|
3261
|
+
if (!isJsonMode()) {
|
|
3262
|
+
log(
|
|
3263
|
+
colors.dim(
|
|
3264
|
+
"Couldn't open the browser automatically \u2014 use the payment link to complete checkout."
|
|
3265
|
+
)
|
|
3266
|
+
);
|
|
3267
|
+
}
|
|
3249
3268
|
}
|
|
3250
3269
|
};
|
|
3251
3270
|
}
|
|
@@ -3301,12 +3320,9 @@ function registerAuthCommands(program2) {
|
|
|
3301
3320
|
const authServer = await startAuthServer();
|
|
3302
3321
|
const callbackUrl = `http://localhost:${authServer.port}/callback?state=${encodeURIComponent(authServer.state)}`;
|
|
3303
3322
|
const authUrl = `${apiUrl}/cli-authorize?callback=${encodeURIComponent(callbackUrl)}`;
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
}
|
|
3307
|
-
authServer.close();
|
|
3308
|
-
refuseBrowserAuthForAgent("login");
|
|
3309
|
-
}
|
|
3323
|
+
await openInBrowser(authUrl, {
|
|
3324
|
+
hint: "If the browser didn't open, visit this URL to authenticate:"
|
|
3325
|
+
});
|
|
3310
3326
|
const _spinner = startSpinner("Waiting for authentication...");
|
|
3311
3327
|
try {
|
|
3312
3328
|
const authData = await authServer.waitForCallback();
|
|
@@ -3415,12 +3431,9 @@ function registerAuthCommands(program2) {
|
|
|
3415
3431
|
const authServer = await startAuthServer();
|
|
3416
3432
|
const callbackUrl = `http://localhost:${authServer.port}/callback?state=${encodeURIComponent(authServer.state)}`;
|
|
3417
3433
|
const authUrl = `${apiUrl}/cli-authorize?action=register&callback=${encodeURIComponent(callbackUrl)}`;
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
}
|
|
3421
|
-
authServer.close();
|
|
3422
|
-
refuseBrowserAuthForAgent("register");
|
|
3423
|
-
}
|
|
3434
|
+
await openInBrowser(authUrl, {
|
|
3435
|
+
hint: "If the browser didn't open, visit this URL to create your account:"
|
|
3436
|
+
});
|
|
3424
3437
|
const _spinner = startSpinner("Waiting for account creation...");
|
|
3425
3438
|
try {
|
|
3426
3439
|
const authData = await authServer.waitForCallback();
|
|
@@ -3964,6 +3977,18 @@ import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
|
|
|
3964
3977
|
|
|
3965
3978
|
// src/lib/billing-upgrade.ts
|
|
3966
3979
|
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.`;
|
|
3980
|
+
function storageSlotTierForAddonKey(addonKey) {
|
|
3981
|
+
switch (addonKey) {
|
|
3982
|
+
case "storage.starter":
|
|
3983
|
+
return "STARTER";
|
|
3984
|
+
case "storage.standard":
|
|
3985
|
+
return "STANDARD";
|
|
3986
|
+
case "storage.pro":
|
|
3987
|
+
return "PRO";
|
|
3988
|
+
default:
|
|
3989
|
+
return null;
|
|
3990
|
+
}
|
|
3991
|
+
}
|
|
3967
3992
|
function resolveTarget(input2) {
|
|
3968
3993
|
if (input2.kind === "plan") return input2.planKey ?? "";
|
|
3969
3994
|
if (input2.kind === "addon") {
|
|
@@ -3990,8 +4015,16 @@ async function performBillingChange(client, input2) {
|
|
|
3990
4015
|
addons: input2.addons
|
|
3991
4016
|
});
|
|
3992
4017
|
} else if (kind === "addon") {
|
|
3993
|
-
const
|
|
3994
|
-
|
|
4018
|
+
const storageTier = !input2.addons && input2.addonKey ? storageSlotTierForAddonKey(input2.addonKey) : null;
|
|
4019
|
+
if (storageTier) {
|
|
4020
|
+
result = await client.storage.purchaseStorageSlot.mutate({
|
|
4021
|
+
tier: storageTier,
|
|
4022
|
+
quantity: input2.quantity ?? 1
|
|
4023
|
+
});
|
|
4024
|
+
} else {
|
|
4025
|
+
const items = input2.addons ?? (input2.addonKey ? [{ addonKey: input2.addonKey, quantity: input2.quantity ?? 1 }] : []);
|
|
4026
|
+
result = await client.subscription.purchaseAddons.mutate({ items });
|
|
4027
|
+
}
|
|
3995
4028
|
} else {
|
|
3996
4029
|
result = await client.subscription.setPlanQuantity.mutate({
|
|
3997
4030
|
quantity: input2.quantity
|
|
@@ -4230,6 +4263,23 @@ function isConflictError(err) {
|
|
|
4230
4263
|
const e = err;
|
|
4231
4264
|
return (e?.code ?? e?.data?.code) === "CONFLICT";
|
|
4232
4265
|
}
|
|
4266
|
+
async function previewAddonAmountDue(client, addonKey, quantity) {
|
|
4267
|
+
try {
|
|
4268
|
+
if (storageSlotTierForAddonKey(addonKey)) {
|
|
4269
|
+
const catalog = await client.subscription.getCatalog.query();
|
|
4270
|
+
const addon = (catalog?.addons ?? []).find(
|
|
4271
|
+
(a) => (a.key ?? a.addonKey) === addonKey
|
|
4272
|
+
);
|
|
4273
|
+
return typeof addon?.priceHalalas === "number" ? addon.priceHalalas * quantity : void 0;
|
|
4274
|
+
}
|
|
4275
|
+
const preview = await client.subscription.previewAddonsPurchase.query({
|
|
4276
|
+
items: [{ addonKey, quantity }]
|
|
4277
|
+
});
|
|
4278
|
+
return typeof preview?.totalProratedHalalas === "number" ? preview.totalProratedHalalas : void 0;
|
|
4279
|
+
} catch {
|
|
4280
|
+
return void 0;
|
|
4281
|
+
}
|
|
4282
|
+
}
|
|
4233
4283
|
function registerBillingCommands(program2) {
|
|
4234
4284
|
const billing = program2.command("billing").description("Manage subscription and billing");
|
|
4235
4285
|
billing.command("status").description("Show current subscription and entitlements").action(async () => {
|
|
@@ -4617,26 +4667,38 @@ function registerBillingCommands(program2) {
|
|
|
4617
4667
|
try {
|
|
4618
4668
|
if (!isLoggedIn()) throw new AuthError();
|
|
4619
4669
|
const quantity = options.quantity || 1;
|
|
4670
|
+
const client = getApiClient();
|
|
4620
4671
|
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
|
-
}
|
|
4672
|
+
const amountDueHalalas = await previewAddonAmountDue(
|
|
4673
|
+
client,
|
|
4674
|
+
addonKey,
|
|
4675
|
+
quantity
|
|
4633
4676
|
);
|
|
4634
|
-
if (
|
|
4635
|
-
log(
|
|
4636
|
-
|
|
4677
|
+
if (shouldAutoConfirmPaidCheckout(amountDueHalalas)) {
|
|
4678
|
+
log(
|
|
4679
|
+
`
|
|
4680
|
+
Adding ${colors.cyan(addonKey)} \xD7 ${quantity} \u2014 opening the secure payment page...`
|
|
4681
|
+
);
|
|
4682
|
+
} else {
|
|
4683
|
+
log("");
|
|
4684
|
+
log(`Addon: ${colors.cyan(addonKey)}`);
|
|
4685
|
+
log(`Quantity: ${quantity}`);
|
|
4686
|
+
log("");
|
|
4687
|
+
const confirmed = await confirm(
|
|
4688
|
+
`Add addon "${addonKey}" \xD7 ${quantity}?`,
|
|
4689
|
+
false,
|
|
4690
|
+
{
|
|
4691
|
+
field: "confirm_addon_add",
|
|
4692
|
+
flag: "--yes",
|
|
4693
|
+
context: { addonKey, quantity, amountDueHalalas }
|
|
4694
|
+
}
|
|
4695
|
+
);
|
|
4696
|
+
if (!confirmed) {
|
|
4697
|
+
log("Cancelled.");
|
|
4698
|
+
return;
|
|
4699
|
+
}
|
|
4637
4700
|
}
|
|
4638
4701
|
}
|
|
4639
|
-
const client = getApiClient();
|
|
4640
4702
|
const _spinner = startSpinner("Adding addon...");
|
|
4641
4703
|
let raw;
|
|
4642
4704
|
try {
|
|
@@ -4792,20 +4854,32 @@ function registerBillingCommands(program2) {
|
|
|
4792
4854
|
try {
|
|
4793
4855
|
if (!isLoggedIn()) throw new AuthError();
|
|
4794
4856
|
const quantity = options.quantity || 1;
|
|
4857
|
+
const client = getApiClient();
|
|
4795
4858
|
if (!shouldSkipConfirmation()) {
|
|
4796
|
-
|
|
4859
|
+
const amountDueHalalas = await previewAddonAmountDue(
|
|
4860
|
+
client,
|
|
4861
|
+
addonKey,
|
|
4862
|
+
quantity
|
|
4863
|
+
);
|
|
4864
|
+
if (shouldAutoConfirmPaidCheckout(amountDueHalalas)) {
|
|
4865
|
+
log(
|
|
4866
|
+
`
|
|
4867
|
+
Purchasing ${quantity}\xD7 ${colors.cyan(addonKey)} \u2014 opening the secure payment page...`
|
|
4868
|
+
);
|
|
4869
|
+
} else {
|
|
4870
|
+
log(`
|
|
4797
4871
|
Purchase ${quantity}\xD7 ${colors.cyan(addonKey)}?`);
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4872
|
+
const confirmed = await confirm("Proceed?", false, {
|
|
4873
|
+
field: "confirm_addon_buy",
|
|
4874
|
+
flag: "--yes",
|
|
4875
|
+
context: { addonKey, quantity, amountDueHalalas }
|
|
4876
|
+
});
|
|
4877
|
+
if (!confirmed) {
|
|
4878
|
+
log("Cancelled.");
|
|
4879
|
+
return;
|
|
4880
|
+
}
|
|
4806
4881
|
}
|
|
4807
4882
|
}
|
|
4808
|
-
const client = getApiClient();
|
|
4809
4883
|
const _spinner = startSpinner("Purchasing addon...");
|
|
4810
4884
|
const result = await performBillingChange(client, {
|
|
4811
4885
|
kind: "addon",
|
|
@@ -5735,7 +5809,7 @@ import {
|
|
|
5735
5809
|
import { tmpdir } from "os";
|
|
5736
5810
|
import { basename, dirname, join as join4 } from "path";
|
|
5737
5811
|
import { promisify } from "util";
|
|
5738
|
-
import
|
|
5812
|
+
import open3 from "open";
|
|
5739
5813
|
|
|
5740
5814
|
// src/lib/agent-setup.ts
|
|
5741
5815
|
var SETUP_HINT = "Run `tarout agent init` to allowlist Bash(tarout:*) so tarout commands run without per-command approval prompts.";
|
|
@@ -5782,6 +5856,21 @@ function nextPlanForRequested(requested) {
|
|
|
5782
5856
|
if (r === "dedicated_large") return "dedicated_large";
|
|
5783
5857
|
return r;
|
|
5784
5858
|
}
|
|
5859
|
+
function nextPlanForCurrent(currentPlanKey) {
|
|
5860
|
+
const family = planFamily(currentPlanKey);
|
|
5861
|
+
if (family === "SHARED") return "dedicated_small";
|
|
5862
|
+
if (family === "DEDICATED") {
|
|
5863
|
+
const k = (currentPlanKey ?? "").trim().toLowerCase();
|
|
5864
|
+
if (k === "dedicated_large") return "dedicated_large";
|
|
5865
|
+
if (k === "dedicated_medium") return "dedicated_large";
|
|
5866
|
+
return "dedicated_medium";
|
|
5867
|
+
}
|
|
5868
|
+
return "shared";
|
|
5869
|
+
}
|
|
5870
|
+
function upgradeTargetPlan(opts) {
|
|
5871
|
+
if (opts?.currentPlanKey) return nextPlanForCurrent(opts.currentPlanKey);
|
|
5872
|
+
return nextPlanForRequested(opts?.requestedPlan);
|
|
5873
|
+
}
|
|
5785
5874
|
function resolveEntitlementRemedy(failedKey, catalog, opts) {
|
|
5786
5875
|
const plans = catalog?.plans ?? [];
|
|
5787
5876
|
const addons = catalog?.addons ?? [];
|
|
@@ -5801,7 +5890,7 @@ function resolveEntitlementRemedy(failedKey, catalog, opts) {
|
|
|
5801
5890
|
}
|
|
5802
5891
|
if (failedKey?.startsWith("app.dedicated") || failedKey?.startsWith("host.")) {
|
|
5803
5892
|
const requested = opts?.requestedPlan;
|
|
5804
|
-
const target2 = requested && requested.toLowerCase().startsWith("dedicated") ? nextPlanForRequested(requested) : "dedicated_small";
|
|
5893
|
+
const target2 = planFamily(opts?.currentPlanKey) === "DEDICATED" ? nextPlanForCurrent(opts?.currentPlanKey) : requested && requested.toLowerCase().startsWith("dedicated") ? nextPlanForRequested(requested) : "dedicated_small";
|
|
5805
5894
|
const plan2 = plans.find((p) => planKeyOf(p) === target2);
|
|
5806
5895
|
return {
|
|
5807
5896
|
kind: "plan",
|
|
@@ -5814,7 +5903,7 @@ function resolveEntitlementRemedy(failedKey, catalog, opts) {
|
|
|
5814
5903
|
};
|
|
5815
5904
|
}
|
|
5816
5905
|
if (failedKey === "db.free.slots" || failedKey === "storage.free.slots") {
|
|
5817
|
-
const target2 =
|
|
5906
|
+
const target2 = upgradeTargetPlan(opts);
|
|
5818
5907
|
const plan2 = plans.find((p) => planKeyOf(p) === target2);
|
|
5819
5908
|
const resource = failedKey === "db.free.slots" ? "database" : "storage bucket";
|
|
5820
5909
|
return {
|
|
@@ -5842,7 +5931,7 @@ function resolveEntitlementRemedy(failedKey, catalog, opts) {
|
|
|
5842
5931
|
hint: `Add a ${matched?.name ?? targetKey} slot with an addon purchase.`
|
|
5843
5932
|
};
|
|
5844
5933
|
}
|
|
5845
|
-
const target =
|
|
5934
|
+
const target = upgradeTargetPlan(opts);
|
|
5846
5935
|
const plan = plans.find((p) => planKeyOf(p) === target);
|
|
5847
5936
|
return {
|
|
5848
5937
|
kind: "plan",
|
|
@@ -6018,7 +6107,9 @@ async function authenticateViaBrowser(action, apiUrl) {
|
|
|
6018
6107
|
log(
|
|
6019
6108
|
action === "register" ? "Opening browser to create your account..." : "Opening browser to authenticate..."
|
|
6020
6109
|
);
|
|
6021
|
-
await
|
|
6110
|
+
await openInBrowser(authUrl, {
|
|
6111
|
+
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:"
|
|
6112
|
+
});
|
|
6022
6113
|
const _spinner = startSpinner(
|
|
6023
6114
|
action === "register" ? "Waiting for account creation..." : "Waiting for authentication..."
|
|
6024
6115
|
);
|
|
@@ -6705,6 +6796,14 @@ async function getCurrentPlanQuantitySafely(client) {
|
|
|
6705
6796
|
return 1;
|
|
6706
6797
|
}
|
|
6707
6798
|
}
|
|
6799
|
+
async function getCurrentPlanKeySafely(client) {
|
|
6800
|
+
try {
|
|
6801
|
+
const sub = await client.subscription.getCurrent.query();
|
|
6802
|
+
return sub?.planKey ?? "free";
|
|
6803
|
+
} catch {
|
|
6804
|
+
return void 0;
|
|
6805
|
+
}
|
|
6806
|
+
}
|
|
6708
6807
|
async function runInlineTargetedRemedy(client, remedy) {
|
|
6709
6808
|
let input2;
|
|
6710
6809
|
let label;
|
|
@@ -6748,13 +6847,29 @@ async function runInlineTargetedRemedy(client, remedy) {
|
|
|
6748
6847
|
}
|
|
6749
6848
|
async function promptEntitlementRemedy(client, err, requestedPlan) {
|
|
6750
6849
|
const failedKey = extractEntitlementKeyFromError(err);
|
|
6751
|
-
const catalog = await
|
|
6752
|
-
|
|
6850
|
+
const [catalog, currentPlanKey] = await Promise.all([
|
|
6851
|
+
fetchCatalogSafely(client),
|
|
6852
|
+
getCurrentPlanKeySafely(client)
|
|
6853
|
+
]);
|
|
6854
|
+
const remedy = resolveEntitlementRemedy(failedKey, catalog, {
|
|
6855
|
+
requestedPlan,
|
|
6856
|
+
currentPlanKey
|
|
6857
|
+
});
|
|
6753
6858
|
if (remedy.kind === "plan") {
|
|
6754
|
-
return promptUpgradeFromEntitlementError(
|
|
6859
|
+
return promptUpgradeFromEntitlementError(
|
|
6860
|
+
client,
|
|
6861
|
+
err,
|
|
6862
|
+
requestedPlan,
|
|
6863
|
+
currentPlanKey
|
|
6864
|
+
);
|
|
6755
6865
|
}
|
|
6756
6866
|
const price = remedy.priceHalalas !== void 0 ? ` (${formatPlanPrice(remedy.priceHalalas)})` : "";
|
|
6757
6867
|
const targetedLabel = remedy.kind === "plan_quantity" ? `Add one more app slot${price}` : `Add just the ${remedy.targetName ?? remedy.targetKey}${price}`;
|
|
6868
|
+
const upgradePlanKey = upgradeTargetPlan({ currentPlanKey, requestedPlan });
|
|
6869
|
+
const upgradePlanDef = (catalog?.plans ?? []).find(
|
|
6870
|
+
(p) => (p.planKey ?? p.key) === upgradePlanKey
|
|
6871
|
+
);
|
|
6872
|
+
const upgradeLabel = `Upgrade to ${upgradePlanDef?.name ?? upgradePlanKey}`;
|
|
6758
6873
|
log("");
|
|
6759
6874
|
log(colors.warn("That resource isn't included in your current plan."));
|
|
6760
6875
|
log("");
|
|
@@ -6764,7 +6879,7 @@ async function promptEntitlementRemedy(client, err, requestedPlan) {
|
|
|
6764
6879
|
const choice = await select(
|
|
6765
6880
|
"How would you like to add it?",
|
|
6766
6881
|
[
|
|
6767
|
-
{ name:
|
|
6882
|
+
{ name: upgradeLabel, value: UPGRADE },
|
|
6768
6883
|
{ name: targetedLabel, value: TARGETED },
|
|
6769
6884
|
{ name: "Cancel", value: CANCEL }
|
|
6770
6885
|
],
|
|
@@ -6775,13 +6890,19 @@ async function promptEntitlementRemedy(client, err, requestedPlan) {
|
|
|
6775
6890
|
failedEntitlementKey: failedKey,
|
|
6776
6891
|
remedyKind: remedy.kind,
|
|
6777
6892
|
targetKey: remedy.targetKey,
|
|
6778
|
-
command: remedy.command
|
|
6893
|
+
command: remedy.command,
|
|
6894
|
+
upgradePlanKey
|
|
6779
6895
|
}
|
|
6780
6896
|
}
|
|
6781
6897
|
);
|
|
6782
6898
|
if (choice === CANCEL) return false;
|
|
6783
6899
|
if (choice === UPGRADE) {
|
|
6784
|
-
return promptUpgradeFromEntitlementError(
|
|
6900
|
+
return promptUpgradeFromEntitlementError(
|
|
6901
|
+
client,
|
|
6902
|
+
err,
|
|
6903
|
+
requestedPlan,
|
|
6904
|
+
currentPlanKey
|
|
6905
|
+
);
|
|
6785
6906
|
}
|
|
6786
6907
|
return runInlineTargetedRemedy(client, remedy);
|
|
6787
6908
|
}
|
|
@@ -6875,9 +6996,9 @@ function extractEntitlementKeyFromError(err) {
|
|
|
6875
6996
|
const m = msg.match(/Plan limit reached for ([\w.]+)/i);
|
|
6876
6997
|
return m?.[1];
|
|
6877
6998
|
}
|
|
6878
|
-
function buildRemedyOptions(remedy, requestedPlan, catalog) {
|
|
6999
|
+
function buildRemedyOptions(remedy, requestedPlan, catalog, currentPlanKey) {
|
|
6879
7000
|
if (remedy.kind === "addon" || remedy.kind === "plan_quantity") {
|
|
6880
|
-
const upgradePlan =
|
|
7001
|
+
const upgradePlan = upgradeTargetPlan({ currentPlanKey, requestedPlan });
|
|
6881
7002
|
const planDef = (catalog?.plans ?? []).find(
|
|
6882
7003
|
(p) => (p.planKey ?? p.key) === upgradePlan
|
|
6883
7004
|
);
|
|
@@ -6905,9 +7026,20 @@ function buildRemedyOptions(remedy, requestedPlan, catalog) {
|
|
|
6905
7026
|
async function emitNeedsUpgrade(client, err, requestedPlan, retryCommand) {
|
|
6906
7027
|
const message = err instanceof Error ? err.message : "Plan upgrade required";
|
|
6907
7028
|
const failedKey = extractEntitlementKeyFromError(err);
|
|
6908
|
-
const catalog = await
|
|
6909
|
-
|
|
6910
|
-
|
|
7029
|
+
const [catalog, currentPlanKey] = await Promise.all([
|
|
7030
|
+
fetchCatalogSafely(client),
|
|
7031
|
+
getCurrentPlanKeySafely(client)
|
|
7032
|
+
]);
|
|
7033
|
+
const remedy = resolveEntitlementRemedy(failedKey, catalog, {
|
|
7034
|
+
requestedPlan,
|
|
7035
|
+
currentPlanKey
|
|
7036
|
+
});
|
|
7037
|
+
const options = buildRemedyOptions(
|
|
7038
|
+
remedy,
|
|
7039
|
+
requestedPlan,
|
|
7040
|
+
catalog,
|
|
7041
|
+
currentPlanKey
|
|
7042
|
+
);
|
|
6911
7043
|
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}\`. Then retry: ${retryCommand}.` : `${remedy.hint} Then retry: ${retryCommand}.`;
|
|
6912
7044
|
outputError("NEEDS_UPGRADE", message, {
|
|
6913
7045
|
failedEntitlementKey: failedKey,
|
|
@@ -6979,7 +7111,7 @@ async function explainFreeResourceSlotExhaustion(client, failedKey) {
|
|
|
6979
7111
|
log(` \u2022 Upgrade: ${colors.cyan("tarout billing upgrade shared --wait")}`);
|
|
6980
7112
|
log("");
|
|
6981
7113
|
}
|
|
6982
|
-
async function promptUpgradeFromEntitlementError(client, err, requestedPlan) {
|
|
7114
|
+
async function promptUpgradeFromEntitlementError(client, err, requestedPlan, currentPlanKey) {
|
|
6983
7115
|
const catalog = await fetchCatalogSafely(client);
|
|
6984
7116
|
const allPlans = catalog?.plans ?? [];
|
|
6985
7117
|
const upgradeable = allPlans.filter(
|
|
@@ -6989,10 +7121,14 @@ async function promptUpgradeFromEntitlementError(client, err, requestedPlan) {
|
|
|
6989
7121
|
return false;
|
|
6990
7122
|
}
|
|
6991
7123
|
const failedKey = extractEntitlementKeyFromError(err);
|
|
7124
|
+
const ladderKey = upgradeTargetPlan({ currentPlanKey, requestedPlan });
|
|
7125
|
+
const ladderPlan = upgradeable.find(
|
|
7126
|
+
(p) => (p.planKey || p.key) === ladderKey
|
|
7127
|
+
);
|
|
6992
7128
|
const matchingPlan = failedKey ? upgradeable.find(
|
|
6993
7129
|
(p) => p.grants?.some((g) => g.entitlementKey === failedKey)
|
|
6994
7130
|
) : void 0;
|
|
6995
|
-
const recommendedKey = matchingPlan ? matchingPlan.planKey || matchingPlan.key : inferSuggestedPlan(requestedPlan);
|
|
7131
|
+
const recommendedKey = ladderPlan ? ladderKey : matchingPlan ? matchingPlan.planKey || matchingPlan.key : inferSuggestedPlan(requestedPlan);
|
|
6996
7132
|
log("");
|
|
6997
7133
|
log(colors.bold("Available upgrade plans:"));
|
|
6998
7134
|
log("");
|
|
@@ -7122,7 +7258,7 @@ async function openGitProviderSetup() {
|
|
|
7122
7258
|
log(
|
|
7123
7259
|
`No GitHub account is required if you keep using ${colors.dim("--source upload")}.`
|
|
7124
7260
|
);
|
|
7125
|
-
await
|
|
7261
|
+
await open3(url);
|
|
7126
7262
|
}
|
|
7127
7263
|
async function configureOptionalResources(client, profile, app, options, inspection) {
|
|
7128
7264
|
const database = await resolveDatabaseChoice(options, inspection);
|
|
@@ -9033,6 +9169,39 @@ function registerDbCommands(program2) {
|
|
|
9033
9169
|
}
|
|
9034
9170
|
log("");
|
|
9035
9171
|
} catch (err) {
|
|
9172
|
+
if (isEntitlementError(err)) {
|
|
9173
|
+
if (isJsonMode() || isNonInteractiveMode() || shouldSkipConfirmation()) {
|
|
9174
|
+
await emitNeedsUpgrade(
|
|
9175
|
+
getApiClient(),
|
|
9176
|
+
err,
|
|
9177
|
+
void 0,
|
|
9178
|
+
"tarout db create"
|
|
9179
|
+
);
|
|
9180
|
+
exit(ExitCode.PERMISSION_DENIED);
|
|
9181
|
+
}
|
|
9182
|
+
const message = err instanceof Error ? err.message : "Plan upgrade required";
|
|
9183
|
+
log("");
|
|
9184
|
+
log(colors.warn(message));
|
|
9185
|
+
const upgraded = await promptEntitlementRemedy(
|
|
9186
|
+
getApiClient(),
|
|
9187
|
+
err,
|
|
9188
|
+
void 0
|
|
9189
|
+
);
|
|
9190
|
+
if (!upgraded) {
|
|
9191
|
+
await emitNeedsUpgrade(
|
|
9192
|
+
getApiClient(),
|
|
9193
|
+
err,
|
|
9194
|
+
void 0,
|
|
9195
|
+
"tarout db create"
|
|
9196
|
+
);
|
|
9197
|
+
exit(ExitCode.PERMISSION_DENIED);
|
|
9198
|
+
}
|
|
9199
|
+
box("Billing updated", [
|
|
9200
|
+
colors.success("Subscription updated."),
|
|
9201
|
+
`Run ${colors.cyan("tarout db create")} again to create the database.`
|
|
9202
|
+
]);
|
|
9203
|
+
return;
|
|
9204
|
+
}
|
|
9036
9205
|
handleError(err);
|
|
9037
9206
|
}
|
|
9038
9207
|
});
|
|
@@ -13291,13 +13460,12 @@ function registerInitCommand(program2) {
|
|
|
13291
13460
|
});
|
|
13292
13461
|
} catch (err) {
|
|
13293
13462
|
if (isEntitlementError(err)) {
|
|
13294
|
-
|
|
13295
|
-
|
|
13296
|
-
|
|
13297
|
-
|
|
13298
|
-
|
|
13299
|
-
|
|
13300
|
-
});
|
|
13463
|
+
await emitNeedsUpgrade(
|
|
13464
|
+
getApiClient(),
|
|
13465
|
+
err,
|
|
13466
|
+
options.plan,
|
|
13467
|
+
"tarout init"
|
|
13468
|
+
);
|
|
13301
13469
|
exit(ExitCode.PERMISSION_DENIED);
|
|
13302
13470
|
}
|
|
13303
13471
|
throw err;
|
|
@@ -15527,7 +15695,7 @@ function registerProjectsCommands(program2) {
|
|
|
15527
15695
|
}
|
|
15528
15696
|
|
|
15529
15697
|
// src/commands/providers.ts
|
|
15530
|
-
import
|
|
15698
|
+
import open4 from "open";
|
|
15531
15699
|
function registerProvidersCommands(program2) {
|
|
15532
15700
|
const providers = program2.command("providers").description("Manage Git providers (GitHub, GitLab, Bitbucket)");
|
|
15533
15701
|
providers.command("list").alias("ls").description("List all connected Git providers").action(async () => {
|
|
@@ -15644,7 +15812,7 @@ function registerProvidersCommands(program2) {
|
|
|
15644
15812
|
log(
|
|
15645
15813
|
"Complete the GitHub browser flow, then connect the repository to your app."
|
|
15646
15814
|
);
|
|
15647
|
-
await
|
|
15815
|
+
await open4(url);
|
|
15648
15816
|
} catch (err) {
|
|
15649
15817
|
handleError(err);
|
|
15650
15818
|
}
|
|
@@ -20112,11 +20280,16 @@ function registerWalletCommands(program2) {
|
|
|
20112
20280
|
outputData(result);
|
|
20113
20281
|
return;
|
|
20114
20282
|
}
|
|
20283
|
+
const paymentUrl = result.paymentUrl || result.url || "";
|
|
20115
20284
|
box("Wallet Top-Up", [
|
|
20116
20285
|
`Order ID: ${colors.cyan(result.orderId || "")}`,
|
|
20117
20286
|
`Amount: ${result.amount ? `${(result.amount / 100).toFixed(2)} SAR` : "Default"}`,
|
|
20118
|
-
`Payment URL: ${colors.cyan(
|
|
20287
|
+
`Payment URL: ${colors.cyan(paymentUrl)}`
|
|
20119
20288
|
]);
|
|
20289
|
+
if (paymentUrl) {
|
|
20290
|
+
const openPayment = paymentBrowserOpener();
|
|
20291
|
+
if (openPayment) await openPayment(paymentUrl);
|
|
20292
|
+
}
|
|
20120
20293
|
log("Complete payment in your browser to credit the wallet.");
|
|
20121
20294
|
log(
|
|
20122
20295
|
`Confirm after payment: ${colors.dim(`tarout wallet confirm ${result.orderId || "<orderId>"}`)}`
|