@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.
Files changed (2) hide show
  1. package/dist/index.js +246 -73
  2. 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.0",
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
- try {
3305
- await open3(authUrl);
3306
- } catch {
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
- try {
3419
- await open3(authUrl);
3420
- } catch {
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 items = input2.addons ?? (input2.addonKey ? [{ addonKey: input2.addonKey, quantity: input2.quantity ?? 1 }] : []);
3994
- result = await client.subscription.purchaseAddons.mutate({ items });
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
- log("");
4622
- log(`Addon: ${colors.cyan(addonKey)}`);
4623
- log(`Quantity: ${quantity}`);
4624
- log("");
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 (!confirmed) {
4635
- log("Cancelled.");
4636
- return;
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
- log(`
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
- const confirmed = await confirm("Proceed?", false, {
4799
- field: "confirm_addon_buy",
4800
- flag: "--yes",
4801
- context: { addonKey, quantity }
4802
- });
4803
- if (!confirmed) {
4804
- log("Cancelled.");
4805
- return;
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 open4 from "open";
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 = nextPlanForRequested(opts?.requestedPlan);
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 = nextPlanForRequested(opts?.requestedPlan);
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 open4(authUrl);
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 fetchCatalogSafely(client);
6752
- const remedy = resolveEntitlementRemedy(failedKey, catalog, { requestedPlan });
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(client, err, requestedPlan);
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: "Upgrade the plan", value: UPGRADE },
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(client, err, requestedPlan);
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 = nextPlanForRequested(requestedPlan);
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 fetchCatalogSafely(client);
6909
- const remedy = resolveEntitlementRemedy(failedKey, catalog, { requestedPlan });
6910
- const options = buildRemedyOptions(remedy, requestedPlan, catalog);
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 open4(url);
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
- const message = err instanceof Error ? err.message : "Plan upgrade required";
13295
- outputError("NEEDS_UPGRADE", message, {
13296
- suggestedPlan: inferSuggestedPlan(options.plan),
13297
- failedEntitlementKey: extractEntitlementKeyFromError(err),
13298
- hint: "Run `tarout billing upgrade <plan> --wait` to add slots, then retry `tarout init`.",
13299
- permissionHint: AGENT_BILLING_PERMISSION_HINT
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 open5 from "open";
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 open5(url);
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(result.paymentUrl || result.url || "")}`
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>"}`)}`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tarout/cli",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "description": "Tarout CLI — the Saudi cloud platform for coding agents",
5
5
  "type": "module",
6
6
  "bin": {