@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.
- package/dist/index.js +101 -46
- 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) {
|
|
@@ -3274,21 +3278,24 @@ function shouldAutoConfirmPaidCheckout(amountDueHalalas) {
|
|
|
3274
3278
|
}
|
|
3275
3279
|
|
|
3276
3280
|
// src/commands/auth.ts
|
|
3277
|
-
function
|
|
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
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3283
|
+
outputJsonLine({
|
|
3284
|
+
type: "event",
|
|
3285
|
+
event: "auth_url",
|
|
3286
|
+
authUrl,
|
|
3287
|
+
browserLaunched: launched,
|
|
3288
|
+
callbackPort
|
|
3286
3289
|
});
|
|
3287
|
-
|
|
3288
|
-
|
|
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
|
|
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
|
-
"-
|
|
4382
|
-
"
|
|
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
|
-
"-
|
|
4660
|
-
"
|
|
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
|
-
"-
|
|
4773
|
-
"
|
|
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
|
-
"-
|
|
4847
|
-
"
|
|
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}\`.
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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)"
|