@tarout/cli 0.10.1 → 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.
Files changed (2) hide show
  1. package/dist/index.js +101 -46
  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.1",
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. On a \`NEEDS_UPGRADE\`
757
- error, surface it to the user and follow the upgrade flow instead of auto-retrying.
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) {
@@ -3274,21 +3278,24 @@ function shouldAutoConfirmPaidCheckout(amountDueHalalas) {
3274
3278
  }
3275
3279
 
3276
3280
  // src/commands/auth.ts
3277
- function refuseBrowserAuthForAgent(action) {
3278
- 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) {
3279
3282
  if (isJsonMode()) {
3280
- outputError("AUTH_BOOTSTRAP_REQUIRED", message, {
3281
- action,
3282
- recommendedFlags: [
3283
- "export TAROUT_TOKEN=<token>",
3284
- "tarout token <api-token>"
3285
- ]
3283
+ outputJsonLine({
3284
+ type: "event",
3285
+ event: "auth_url",
3286
+ authUrl,
3287
+ browserLaunched: launched,
3288
+ callbackPort
3286
3289
  });
3287
- } else {
3288
- process.stderr.write(`error: ${message}
3289
- `);
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
+ );
3290
3298
  }
3291
- exit(ExitCode.AUTH_ERROR);
3292
3299
  }
3293
3300
  function registerAuthCommands(program2) {
3294
3301
  program2.command("login").description("Authenticate with Tarout via browser").option("--api-url <url>", "Custom API URL", "https://tarout.sa").action(async (options) => {
@@ -3311,18 +3318,16 @@ function registerAuthCommands(program2) {
3311
3318
  return;
3312
3319
  }
3313
3320
  }
3314
- if (isJsonMode() || !canLaunchBrowser()) {
3315
- refuseBrowserAuthForAgent("login");
3316
- }
3317
3321
  const apiUrl = options.apiUrl;
3318
3322
  log("");
3319
3323
  log("Opening browser to authenticate...");
3320
3324
  const authServer = await startAuthServer();
3321
3325
  const callbackUrl = `http://localhost:${authServer.port}/callback?state=${encodeURIComponent(authServer.state)}`;
3322
3326
  const authUrl = `${apiUrl}/cli-authorize?callback=${encodeURIComponent(callbackUrl)}`;
3323
- await openInBrowser(authUrl, {
3327
+ const launched = await openInBrowser(authUrl, {
3324
3328
  hint: "If the browser didn't open, visit this URL to authenticate:"
3325
3329
  });
3330
+ announceAuthUrl(authUrl, authServer.port, launched);
3326
3331
  const _spinner = startSpinner("Waiting for authentication...");
3327
3332
  try {
3328
3333
  const authData = await authServer.waitForCallback();
@@ -3422,18 +3427,16 @@ function registerAuthCommands(program2) {
3422
3427
  return;
3423
3428
  }
3424
3429
  }
3425
- if (isJsonMode() || !canLaunchBrowser()) {
3426
- refuseBrowserAuthForAgent("register");
3427
- }
3428
3430
  const apiUrl = options.apiUrl;
3429
3431
  log("");
3430
3432
  log("Opening browser to create your account...");
3431
3433
  const authServer = await startAuthServer();
3432
3434
  const callbackUrl = `http://localhost:${authServer.port}/callback?state=${encodeURIComponent(authServer.state)}`;
3433
3435
  const authUrl = `${apiUrl}/cli-authorize?action=register&callback=${encodeURIComponent(callbackUrl)}`;
3434
- await openInBrowser(authUrl, {
3436
+ const launched = await openInBrowser(authUrl, {
3435
3437
  hint: "If the browser didn't open, visit this URL to create your account:"
3436
3438
  });
3439
+ announceAuthUrl(authUrl, authServer.port, launched);
3437
3440
  const _spinner = startSpinner("Waiting for account creation...");
3438
3441
  try {
3439
3442
  const authData = await authServer.waitForCallback();
@@ -4144,7 +4147,7 @@ function emitBillingResult(result, opts) {
4144
4147
  case "payment_required":
4145
4148
  outputData({
4146
4149
  ...envelope,
4147
- hint: `Open paymentUrl to complete checkout, then run \`${nextCommand}\` \u2014 or re-run with --wait.`
4150
+ hint: `Open paymentUrl to complete checkout, then run \`${nextCommand}\` \u2014 or re-run without --no-wait (waiting is the default).`
4148
4151
  });
4149
4152
  box("Payment required", [
4150
4153
  `${label}`,
@@ -4230,6 +4233,16 @@ function isPaidFamily(planKey) {
4230
4233
  const family = planFamily(planKey);
4231
4234
  return family === "SHARED" || family === "DEDICATED";
4232
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
+ }
4233
4246
  function resourceAddonKeysForPlan(planKey) {
4234
4247
  switch (planFamily(planKey)) {
4235
4248
  case "SHARED":
@@ -4378,8 +4391,8 @@ function registerBillingCommands(program2) {
4378
4391
  collectAddon,
4379
4392
  []
4380
4393
  ).option(
4381
- "-w, --wait",
4382
- "After hosted-checkout opens, poll status until the payment is confirmed"
4394
+ "--no-wait",
4395
+ "Return as soon as the hosted checkout opens, without polling for confirmation (default: wait until paid)"
4383
4396
  ).option(
4384
4397
  "--timeout <seconds>",
4385
4398
  "Maximum wait time in seconds (default 600)",
@@ -4656,8 +4669,8 @@ function registerBillingCommands(program2) {
4656
4669
  }
4657
4670
  });
4658
4671
  billing.command("addon:add").argument("<addon>", "Addon key to add").option("-q, --quantity <n>", "Addon quantity", Number.parseInt).option(
4659
- "-w, --wait",
4660
- "After hosted-checkout opens, poll status until the payment is confirmed"
4672
+ "--no-wait",
4673
+ "Return as soon as the hosted checkout opens, without polling for confirmation (default: wait until paid)"
4661
4674
  ).option(
4662
4675
  "--timeout <seconds>",
4663
4676
  "Maximum wait time in seconds (default 600)",
@@ -4769,8 +4782,8 @@ Adding ${colors.cyan(addonKey)} \xD7 ${quantity} \u2014 opening the secure payme
4769
4782
  }
4770
4783
  });
4771
4784
  billing.command("plan:quantity").argument("<quantity>", "New plan quantity", Number.parseInt).option(
4772
- "-w, --wait",
4773
- "After hosted-checkout opens, poll status until the payment is confirmed"
4785
+ "--no-wait",
4786
+ "Return as soon as the hosted checkout opens, without polling for confirmation (default: wait until paid)"
4774
4787
  ).option(
4775
4788
  "--timeout <seconds>",
4776
4789
  "Maximum wait time in seconds (default 600)",
@@ -4843,8 +4856,8 @@ Adding ${colors.cyan(addonKey)} \xD7 ${quantity} \u2014 opening the secure payme
4843
4856
  }
4844
4857
  });
4845
4858
  billing.command("addon:buy").argument("<addon>", "Addon key").option("-q, --quantity <n>", "Quantity", Number.parseInt).option(
4846
- "-w, --wait",
4847
- "After hosted-checkout opens, poll status until the payment is confirmed"
4859
+ "--no-wait",
4860
+ "Return as soon as the hosted checkout opens, without polling for confirmation (default: wait until paid)"
4848
4861
  ).option(
4849
4862
  "--timeout <seconds>",
4850
4863
  "Maximum wait time in seconds (default 600)",
@@ -7040,7 +7053,7 @@ async function emitNeedsUpgrade(client, err, requestedPlan, retryCommand) {
7040
7053
  catalog,
7041
7054
  currentPlanKey
7042
7055
  );
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}.`;
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}.`;
7044
7057
  outputError("NEEDS_UPGRADE", message, {
7045
7058
  failedEntitlementKey: failedKey,
7046
7059
  remedyKind: remedy.kind,
@@ -7411,6 +7424,57 @@ async function resolveResourcePlan(client, kind, value, message) {
7411
7424
  }
7412
7425
  });
7413
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
+ }
7414
7478
  async function createAndAttachDatabase(client, profile, app, input2) {
7415
7479
  const appName = generateResourceSlug(app.name, input2.kind);
7416
7480
  const label = input2.kind === "postgres" ? "Postgres" : "MySQL";
@@ -7548,12 +7612,7 @@ async function resolveDatabaseProvisioning(client, profile, app, kind, options)
7548
7612
  }
7549
7613
  );
7550
7614
  if (picked === "__create__") {
7551
- const plan = requestedPlan ?? await resolveResourcePlan(
7552
- client,
7553
- "database",
7554
- void 0,
7555
- "Database plan:"
7556
- );
7615
+ const plan = await resolveDatabasePlanOrExit(client, requestedPlan);
7557
7616
  return {
7558
7617
  action: "create",
7559
7618
  plan,
@@ -7918,7 +7977,7 @@ async function attachExistingStorage(client, app, decision) {
7918
7977
  }
7919
7978
  }
7920
7979
  async function buildDatabaseCreateDecision(client, app, kind, requestedPlan) {
7921
- const plan = requestedPlan ?? await resolveResourcePlan(client, "database", void 0, "Database plan:");
7980
+ const plan = await resolveDatabasePlanOrExit(client, requestedPlan);
7922
7981
  return {
7923
7982
  action: "create",
7924
7983
  plan,
@@ -9015,10 +9074,7 @@ function normalizeDbPlan(value) {
9015
9074
  );
9016
9075
  }
9017
9076
  async function resolveDbPlan(client, explicit) {
9018
- const normalized = normalizeDbPlan(explicit);
9019
- if (normalized) return normalized;
9020
- const tiers = await loadResourceTiers(client, "database");
9021
- return pickDefaultResourceTier(tiers);
9077
+ return resolveDatabasePlanOrExit(client, normalizeDbPlan(explicit));
9022
9078
  }
9023
9079
  function registerDbCommands(program2) {
9024
9080
  const db = program2.command("db").description("Manage databases");
@@ -13390,8 +13446,7 @@ function registerInitCommand(program2) {
13390
13446
  "Custom API URL (defaults to saved profile or https://tarout.sa)"
13391
13447
  ).option("--token <token>", "API token for this run").option("--name <name>", "Application name (defaults to directory name)").option(
13392
13448
  "--plan <plan>",
13393
- "App hosting plan: free, shared, or dedicated",
13394
- "free"
13449
+ "App hosting plan: free, shared, or dedicated (defaults to your org's subscribed tier)"
13395
13450
  ).option("-r, --region <region>", "Deployment region", DEFAULT_REGION2).option("--description <text>", "Description for the newly created app").option(
13396
13451
  "--database <type>",
13397
13452
  "Provision a database: none, postgres, or mysql (defaults to auto-detected)"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tarout/cli",
3
- "version": "0.10.1",
3
+ "version": "0.11.0",
4
4
  "description": "Tarout CLI — the Saudi cloud platform for coding agents",
5
5
  "type": "module",
6
6
  "bin": {