@tarout/cli 0.7.2 → 0.9.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.
@@ -2,9 +2,9 @@ import {
2
2
  createApiClient,
3
3
  getApiClient,
4
4
  resetApiClient
5
- } from "./chunk-Y6TWR3XZ.js";
5
+ } from "./chunk-CZPJYUNC.js";
6
6
  import "./chunk-5DAFGMBH.js";
7
- import "./chunk-K7JK5HIL.js";
7
+ import "./chunk-WKITMZ4U.js";
8
8
  export {
9
9
  createApiClient,
10
10
  getApiClient,
@@ -10,7 +10,7 @@ import {
10
10
  isJsonMode,
11
11
  jsonError,
12
12
  outputJson
13
- } from "./chunk-K7JK5HIL.js";
13
+ } from "./chunk-WKITMZ4U.js";
14
14
 
15
15
  // src/lib/api.ts
16
16
  import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
@@ -4,7 +4,7 @@ import {
4
4
  isJsonMode,
5
5
  isNonInteractiveMode,
6
6
  outputNeedsInput
7
- } from "./chunk-K7JK5HIL.js";
7
+ } from "./chunk-WKITMZ4U.js";
8
8
 
9
9
  // src/utils/prompts.ts
10
10
  import inquirer from "inquirer";
@@ -106,6 +106,11 @@ function success(message) {
106
106
  console.log(colors.success(`\u2713 ${message}`));
107
107
  }
108
108
  }
109
+ function warn(message) {
110
+ if (!globalOptions.json) {
111
+ console.log(colors.warn(`\u26A0 ${message}`));
112
+ }
113
+ }
109
114
  function error(message, suggestions) {
110
115
  if (globalOptions.json) {
111
116
  outputJson(jsonError("ERROR", message, suggestions));
@@ -197,6 +202,7 @@ export {
197
202
  getStatusBadge,
198
203
  log,
199
204
  success,
205
+ warn,
200
206
  error,
201
207
  table,
202
208
  outputData,
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  handleError,
14
14
  platformFetch,
15
15
  resetApiClient
16
- } from "./chunk-Y6TWR3XZ.js";
16
+ } from "./chunk-CZPJYUNC.js";
17
17
  import {
18
18
  clearConfig,
19
19
  getApiUrl,
@@ -34,7 +34,7 @@ import {
34
34
  password,
35
35
  promptOrEmit,
36
36
  select
37
- } from "./chunk-DI66W4S5.js";
37
+ } from "./chunk-OPPZGNJX.js";
38
38
  import {
39
39
  ExitCode,
40
40
  box,
@@ -52,8 +52,9 @@ import {
52
52
  setGlobalOptions,
53
53
  shouldSkipConfirmation,
54
54
  success,
55
- table
56
- } from "./chunk-K7JK5HIL.js";
55
+ table,
56
+ warn
57
+ } from "./chunk-WKITMZ4U.js";
57
58
 
58
59
  // src/index.ts
59
60
  import { Command } from "commander";
@@ -61,7 +62,7 @@ import { Command } from "commander";
61
62
  // package.json
62
63
  var package_default = {
63
64
  name: "@tarout/cli",
64
- version: "0.7.2",
65
+ version: "0.9.0",
65
66
  description: "Tarout CLI \u2014 the Saudi cloud platform for coding agents",
66
67
  type: "module",
67
68
  bin: {
@@ -632,7 +633,7 @@ Root access: ${hasAccess ? colors.success("yes") : colors.error("no")}
632
633
  try {
633
634
  if (!isLoggedIn()) throw new AuthError();
634
635
  if (!shouldSkipConfirmation()) {
635
- const { confirm: confirmFn } = await import("./prompts-JH6YBHHV.js");
636
+ const { confirm: confirmFn } = await import("./prompts-D72VJYWE.js");
636
637
  const ok = await confirmFn(
637
638
  `Remove user "${userId}" from the organization?`,
638
639
  false,
@@ -659,7 +660,7 @@ Root access: ${hasAccess ? colors.success("yes") : colors.error("no")}
659
660
  account.command("assign-permissions").argument("<user-id>", "User ID").option("--role <role>", "Role: admin, member").description("Assign permissions/role to a member (org owner only)").action(async (userId, options) => {
660
661
  try {
661
662
  if (!isLoggedIn()) throw new AuthError();
662
- const { select: selectFn } = await import("./prompts-JH6YBHHV.js");
663
+ const { select: selectFn } = await import("./prompts-D72VJYWE.js");
663
664
  const role = options.role || await selectFn(
664
665
  "Role:",
665
666
  [
@@ -724,6 +725,192 @@ Root access: ${hasAccess ? colors.success("yes") : colors.error("no")}
724
725
  });
725
726
  }
726
727
 
728
+ // src/commands/agent.ts
729
+ import { resolve } from "path";
730
+
731
+ // src/lib/agent-scaffold.ts
732
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
733
+ import { join } from "path";
734
+ var AGENT_TYPES = [
735
+ "claude",
736
+ "codex",
737
+ "cursor",
738
+ "other"
739
+ ];
740
+ var TAROUT_ALLOW_ENTRY = "Bash(tarout:*)";
741
+ var BLOCK_BEGIN = "<!-- BEGIN TAROUT -->";
742
+ var BLOCK_END = "<!-- END TAROUT -->";
743
+ var TAROUT_AGENT_BLOCK = `${BLOCK_BEGIN}
744
+ ## Tarout
745
+
746
+ This project deploys to [Tarout](https://tarout.sa) (PaaS). Use the **Tarout CLI**
747
+ for all deployment, database, storage, environment-variable, and domain operations \u2014
748
+ don't hand-edit infrastructure.
749
+
750
+ - **Deploy** the current folder: \`tarout up --json --yes\` (inspects \u2192 builds \u2192 deploys;
751
+ read the final JSON envelope's \`success\` and \`data.url\`).
752
+ - **Re-deploy** a linked app: \`tarout deploy --wait\`.
753
+ - **Run locally** with cloud env vars: \`tarout dev\`.
754
+ - **Full agent guide:** https://tarout.sa/docs/for-ai/onboarding
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.
758
+ Ask before destructive actions (delete, rollback, revealing secrets).
759
+ ${BLOCK_END}`;
760
+ function markdownTargetFor(agent) {
761
+ return agent === "claude" ? "CLAUDE.md" : "AGENTS.md";
762
+ }
763
+ function hasMarkers(content) {
764
+ return content.includes(BLOCK_BEGIN) && content.includes(BLOCK_END);
765
+ }
766
+ function replaceBlock(content, block) {
767
+ const begin = content.indexOf(BLOCK_BEGIN);
768
+ const end = content.indexOf(BLOCK_END);
769
+ if (begin === -1 || end === -1 || end < begin) return content;
770
+ return `${content.slice(0, begin)}${block}${content.slice(end + BLOCK_END.length)}`;
771
+ }
772
+ function upsertMarkdownBlock(filePath, block) {
773
+ if (!existsSync(filePath)) {
774
+ writeFileSync(filePath, `${block}
775
+ `, "utf-8");
776
+ return "created";
777
+ }
778
+ const existing = readFileSync(filePath, "utf-8");
779
+ if (hasMarkers(existing)) {
780
+ const replaced = replaceBlock(existing, block);
781
+ if (replaced === existing) return "unchanged";
782
+ writeFileSync(filePath, replaced, "utf-8");
783
+ return "updated";
784
+ }
785
+ const separator = existing.endsWith("\n") ? "\n" : "\n\n";
786
+ writeFileSync(filePath, `${existing}${separator}${block}
787
+ `, "utf-8");
788
+ return "appended";
789
+ }
790
+ function mergeClaudeSettings(claudeDir) {
791
+ const settingsPath = join(claudeDir, "settings.local.json");
792
+ const relPath = join(".claude", "settings.local.json");
793
+ if (!existsSync(settingsPath)) {
794
+ mkdirSync(claudeDir, { recursive: true });
795
+ const settings2 = {
796
+ permissions: { allow: [TAROUT_ALLOW_ENTRY] }
797
+ };
798
+ writeFileSync(settingsPath, `${JSON.stringify(settings2, null, 2)}
799
+ `, "utf-8");
800
+ return { path: relPath, action: "created" };
801
+ }
802
+ let settings;
803
+ try {
804
+ settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
805
+ } catch {
806
+ return {
807
+ path: relPath,
808
+ action: "skipped",
809
+ reason: "existing settings.local.json is not valid JSON; left untouched"
810
+ };
811
+ }
812
+ if (typeof settings !== "object" || settings === null) {
813
+ return {
814
+ path: relPath,
815
+ action: "skipped",
816
+ reason: "existing settings.local.json is not a JSON object; left untouched"
817
+ };
818
+ }
819
+ const permissions = settings.permissions ??= {};
820
+ const allow = Array.isArray(permissions.allow) ? permissions.allow : [];
821
+ if (allow.includes(TAROUT_ALLOW_ENTRY)) {
822
+ return { path: relPath, action: "unchanged" };
823
+ }
824
+ permissions.allow = [...allow, TAROUT_ALLOW_ENTRY];
825
+ writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
826
+ `, "utf-8");
827
+ return { path: relPath, action: "updated" };
828
+ }
829
+ function scaffoldAgentConfig(options) {
830
+ const { cwd, agent } = options;
831
+ const files = [];
832
+ const mdName = markdownTargetFor(agent);
833
+ files.push({
834
+ path: mdName,
835
+ action: upsertMarkdownBlock(join(cwd, mdName), TAROUT_AGENT_BLOCK)
836
+ });
837
+ if (agent === "claude") {
838
+ files.push(mergeClaudeSettings(join(cwd, ".claude")));
839
+ }
840
+ return {
841
+ agent,
842
+ files,
843
+ nextSteps: ["tarout up --json --yes"]
844
+ };
845
+ }
846
+
847
+ // src/commands/agent.ts
848
+ function parseAgent(value) {
849
+ const agent = (value ?? "claude").toLowerCase();
850
+ if (AGENT_TYPES.includes(agent)) {
851
+ return agent;
852
+ }
853
+ throw new CliError(
854
+ `Invalid agent "${value}". Use one of: ${AGENT_TYPES.join(", ")}.`,
855
+ ExitCode.INVALID_ARGUMENTS
856
+ );
857
+ }
858
+ function registerAgentCommands(program2) {
859
+ const agent = program2.command("agent").description("Configure coding agents to use the Tarout CLI");
860
+ agent.command("init").argument("[path]", "Project directory (defaults to current)").description(
861
+ "Write agent instructions (CLAUDE.md/AGENTS.md) and a Bash(tarout:*) permission allowlist so a coding agent can drive Tarout"
862
+ ).option(
863
+ "--agent <type>",
864
+ `Coding agent to configure: ${AGENT_TYPES.join(", ")}`,
865
+ "claude"
866
+ ).action(async (cwdArg, options) => {
867
+ try {
868
+ const cwd = cwdArg ? resolve(cwdArg) : process.cwd();
869
+ const agentType = parseAgent(options.agent);
870
+ const result = scaffoldAgentConfig({ cwd, agent: agentType });
871
+ if (isJsonMode()) {
872
+ for (const file of result.files) {
873
+ outputJsonLine({
874
+ type: "event",
875
+ event: "file_written",
876
+ path: file.path,
877
+ action: file.action,
878
+ ...file.reason ? { reason: file.reason } : {}
879
+ });
880
+ }
881
+ outputJsonLine({
882
+ type: "result",
883
+ ok: true,
884
+ agent: result.agent,
885
+ files: result.files,
886
+ nextSteps: result.nextSteps
887
+ });
888
+ return;
889
+ }
890
+ success("Coding agents configured");
891
+ box(
892
+ `Agent: ${colors.cyan(result.agent)}`,
893
+ result.files.map((file) => {
894
+ const label = `${colors.bold(file.action)} ${file.path}`;
895
+ return file.reason ? `${label} \u2014 ${colors.dim(file.reason)}` : label;
896
+ })
897
+ );
898
+ for (const file of result.files) {
899
+ if (file.action === "skipped") {
900
+ warn(`${file.path} was left untouched (${file.reason}).`);
901
+ }
902
+ }
903
+ log("Next steps:");
904
+ for (const step of result.nextSteps) {
905
+ log(` ${colors.dim(step)}`);
906
+ }
907
+ log("");
908
+ } catch (err) {
909
+ handleError(err);
910
+ }
911
+ });
912
+ }
913
+
727
914
  // src/commands/ai.ts
728
915
  function registerAiCommands(program2) {
729
916
  const ai = program2.command("ai").description("Manage AI Gateway models and API keys");
@@ -1544,7 +1731,7 @@ function registerAppsCommands(program2) {
1544
1731
  throw new NotFoundError("Application", appIdentifier, suggestions);
1545
1732
  }
1546
1733
  if (!shouldSkipConfirmation()) {
1547
- const { confirm: confirm2 } = await import("./prompts-JH6YBHHV.js");
1734
+ const { confirm: confirm2 } = await import("./prompts-D72VJYWE.js");
1548
1735
  const confirmed = await confirm2(
1549
1736
  `Stop application "${app.name}"?`,
1550
1737
  false,
@@ -1862,7 +2049,7 @@ function registerAppsCommands(program2) {
1862
2049
  throw new NotFoundError("Application", appIdentifier);
1863
2050
  }
1864
2051
  if (!shouldSkipConfirmation()) {
1865
- const { confirm: confirm2 } = await import("./prompts-JH6YBHHV.js");
2052
+ const { confirm: confirm2 } = await import("./prompts-D72VJYWE.js");
1866
2053
  const confirmed = await confirm2(
1867
2054
  `Disconnect source provider from "${app.name}"?`,
1868
2055
  false,
@@ -2880,7 +3067,7 @@ function formatTime(ts) {
2880
3067
  }
2881
3068
 
2882
3069
  // src/commands/auth.ts
2883
- import open2 from "open";
3070
+ import open3 from "open";
2884
3071
 
2885
3072
  // src/lib/auth-server.ts
2886
3073
  import { randomBytes, timingSafeEqual } from "crypto";
@@ -2898,7 +3085,7 @@ function escapeHtml(value) {
2898
3085
  return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
2899
3086
  }
2900
3087
  function startAuthServer(options = {}) {
2901
- return new Promise((resolve3) => {
3088
+ return new Promise((resolve4) => {
2902
3089
  const app = express();
2903
3090
  const expectedState = options.state ?? createAuthState();
2904
3091
  let server;
@@ -3022,7 +3209,7 @@ function startAuthServer(options = {}) {
3022
3209
  server = app.listen(0, "127.0.0.1", () => {
3023
3210
  const address = server.address();
3024
3211
  const port = typeof address === "object" && address ? address.port : 0;
3025
- resolve3({
3212
+ resolve4({
3026
3213
  port,
3027
3214
  state: expectedState,
3028
3215
  waitForCallback: () => callbackPromise,
@@ -3032,6 +3219,28 @@ function startAuthServer(options = {}) {
3032
3219
  });
3033
3220
  }
3034
3221
 
3222
+ // src/lib/browser.ts
3223
+ import open2 from "open";
3224
+ function canLaunchBrowser() {
3225
+ if (process.platform === "darwin" || process.platform === "win32") {
3226
+ return true;
3227
+ }
3228
+ return Boolean(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
3229
+ }
3230
+ function paymentBrowserOpener(opts) {
3231
+ if (opts?.noOpen || !canLaunchBrowser()) return void 0;
3232
+ return async (url) => {
3233
+ try {
3234
+ await open2(url);
3235
+ } catch {
3236
+ }
3237
+ };
3238
+ }
3239
+ function shouldAutoConfirmPaidCheckout(amountDueHalalas) {
3240
+ const isPaidCheckout = typeof amountDueHalalas === "number" && amountDueHalalas > 0;
3241
+ return !isJsonMode() && isNonInteractiveMode() && canLaunchBrowser() && isPaidCheckout;
3242
+ }
3243
+
3035
3244
  // src/commands/auth.ts
3036
3245
  function refuseBrowserAuthForAgent(action) {
3037
3246
  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.`;
@@ -3070,7 +3279,7 @@ function registerAuthCommands(program2) {
3070
3279
  return;
3071
3280
  }
3072
3281
  }
3073
- if (isJsonMode() || isNonInteractiveMode()) {
3282
+ if (isJsonMode() || !canLaunchBrowser()) {
3074
3283
  refuseBrowserAuthForAgent("login");
3075
3284
  }
3076
3285
  const apiUrl = options.apiUrl;
@@ -3079,7 +3288,12 @@ function registerAuthCommands(program2) {
3079
3288
  const authServer = await startAuthServer();
3080
3289
  const callbackUrl = `http://localhost:${authServer.port}/callback?state=${encodeURIComponent(authServer.state)}`;
3081
3290
  const authUrl = `${apiUrl}/cli-authorize?callback=${encodeURIComponent(callbackUrl)}`;
3082
- await open2(authUrl);
3291
+ try {
3292
+ await open3(authUrl);
3293
+ } catch {
3294
+ authServer.close();
3295
+ refuseBrowserAuthForAgent("login");
3296
+ }
3083
3297
  const _spinner = startSpinner("Waiting for authentication...");
3084
3298
  try {
3085
3299
  const authData = await authServer.waitForCallback();
@@ -3179,7 +3393,7 @@ function registerAuthCommands(program2) {
3179
3393
  return;
3180
3394
  }
3181
3395
  }
3182
- if (isJsonMode() || isNonInteractiveMode()) {
3396
+ if (isJsonMode() || !canLaunchBrowser()) {
3183
3397
  refuseBrowserAuthForAgent("register");
3184
3398
  }
3185
3399
  const apiUrl = options.apiUrl;
@@ -3188,7 +3402,12 @@ function registerAuthCommands(program2) {
3188
3402
  const authServer = await startAuthServer();
3189
3403
  const callbackUrl = `http://localhost:${authServer.port}/callback?state=${encodeURIComponent(authServer.state)}`;
3190
3404
  const authUrl = `${apiUrl}/cli-authorize?action=register&callback=${encodeURIComponent(callbackUrl)}`;
3191
- await open2(authUrl);
3405
+ try {
3406
+ await open3(authUrl);
3407
+ } catch {
3408
+ authServer.close();
3409
+ refuseBrowserAuthForAgent("register");
3410
+ }
3192
3411
  const _spinner = startSpinner("Waiting for account creation...");
3193
3412
  try {
3194
3413
  const authData = await authServer.waitForCallback();
@@ -3303,7 +3522,7 @@ function registerAuthCommands(program2) {
3303
3522
  () => input("Token name (e.g., ci-deploy):")
3304
3523
  );
3305
3524
  }
3306
- const { getApiClient: getApiClient2 } = await import("./api-QVUO7EWV.js");
3525
+ const { getApiClient: getApiClient2 } = await import("./api-YFXACHOB.js");
3307
3526
  const client = getApiClient2();
3308
3527
  const profile = getCurrentProfile();
3309
3528
  let keyOrg = profile?.organizationId;
@@ -3680,7 +3899,7 @@ function registerBackupsCommands(program2) {
3680
3899
  try {
3681
3900
  if (!isLoggedIn()) throw new AuthError();
3682
3901
  if (!shouldSkipConfirmation()) {
3683
- const { confirm: confirmFn } = await import("./prompts-JH6YBHHV.js");
3902
+ const { confirm: confirmFn } = await import("./prompts-D72VJYWE.js");
3684
3903
  const ok = await confirmFn(
3685
3904
  `Restore backup file "${backupFile}"? This will overwrite current data.`,
3686
3905
  false
@@ -3729,9 +3948,9 @@ function formatBytes2(bytes) {
3729
3948
 
3730
3949
  // src/commands/billing.ts
3731
3950
  import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
3732
- import open3 from "open";
3733
3951
 
3734
3952
  // src/lib/billing-upgrade.ts
3953
+ 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.`;
3735
3954
  function resolveTarget(input2) {
3736
3955
  if (input2.kind === "plan") return input2.planKey ?? "";
3737
3956
  if (input2.kind === "addon") {
@@ -3797,6 +4016,13 @@ async function finalizeBillingMutation(client, result, ctx) {
3797
4016
  }
3798
4017
  const orderId = result.orderId;
3799
4018
  const paymentUrl = result.paymentUrl;
4019
+ if (ctx.wait) ctx.onCheckoutOpened?.({ orderId, paymentUrl });
4020
+ if (ctx.openBrowser) {
4021
+ try {
4022
+ await ctx.openBrowser(paymentUrl);
4023
+ } catch {
4024
+ }
4025
+ }
3800
4026
  if (!ctx.wait) {
3801
4027
  return {
3802
4028
  status: "payment_required",
@@ -3807,13 +4033,6 @@ async function finalizeBillingMutation(client, result, ctx) {
3807
4033
  amountHalalas
3808
4034
  };
3809
4035
  }
3810
- ctx.onCheckoutOpened?.({ orderId, paymentUrl });
3811
- if (ctx.openBrowser) {
3812
- try {
3813
- await ctx.openBrowser(paymentUrl);
3814
- } catch {
3815
- }
3816
- }
3817
4036
  const final = await pollCheckoutUntilTerminal(client, orderId, {
3818
4037
  timeoutMs: ctx.timeoutMs ?? 6e5,
3819
4038
  intervalMs: 4e3
@@ -3994,12 +4213,6 @@ function reportBillingResult(result, label) {
3994
4213
  const code = emitBillingResult(result, { label });
3995
4214
  if (code !== ExitCode.SUCCESS) exit(code);
3996
4215
  }
3997
- function browserOpener(noOpen) {
3998
- if (isJsonMode() || noOpen) return void 0;
3999
- return async (url) => {
4000
- await open3(url);
4001
- };
4002
- }
4003
4216
  function isConflictError(err) {
4004
4217
  const e = err;
4005
4218
  return (e?.code ?? e?.data?.code) === "CONFLICT";
@@ -4205,24 +4418,30 @@ function registerBillingCommands(program2) {
4205
4418
  );
4206
4419
  }
4207
4420
  log("");
4208
- const confirmed = await confirm(
4209
- `Switch to plan "${targetPlan}"?`,
4210
- false,
4211
- {
4212
- field: "confirm_upgrade",
4213
- flag: "--yes",
4214
- context: {
4215
- plan: targetPlan,
4216
- quantity: planQuantity,
4217
- billingPeriod,
4218
- addons,
4219
- amountDueHalalas
4421
+ if (shouldAutoConfirmPaidCheckout(amountDueHalalas)) {
4422
+ log(
4423
+ "Opening the secure payment page in your browser to complete the upgrade..."
4424
+ );
4425
+ } else {
4426
+ const confirmed = await confirm(
4427
+ `Switch to plan "${targetPlan}"?`,
4428
+ false,
4429
+ {
4430
+ field: "confirm_upgrade",
4431
+ flag: "--yes",
4432
+ context: {
4433
+ plan: targetPlan,
4434
+ quantity: planQuantity,
4435
+ billingPeriod,
4436
+ addons,
4437
+ amountDueHalalas
4438
+ }
4220
4439
  }
4440
+ );
4441
+ if (!confirmed) {
4442
+ log("Cancelled.");
4443
+ return;
4221
4444
  }
4222
- );
4223
- if (!confirmed) {
4224
- log("Cancelled.");
4225
- return;
4226
4445
  }
4227
4446
  }
4228
4447
  const _changeSpinner = startSpinner("Changing plan...");
@@ -4234,7 +4453,7 @@ function registerBillingCommands(program2) {
4234
4453
  addons,
4235
4454
  wait: options.wait,
4236
4455
  timeoutMs: options.timeout * 1e3,
4237
- openBrowser: browserOpener(options.open === false),
4456
+ openBrowser: paymentBrowserOpener({ noOpen: options.open === false }),
4238
4457
  onCheckoutOpened: ({ orderId, paymentUrl }) => {
4239
4458
  if (isJsonMode()) {
4240
4459
  outputJsonLine({
@@ -4438,7 +4657,7 @@ function registerBillingCommands(program2) {
4438
4657
  target: addonKey,
4439
4658
  wait: options.wait,
4440
4659
  timeoutMs: options.timeout * 1e3,
4441
- openBrowser: browserOpener(options.open === false)
4660
+ openBrowser: paymentBrowserOpener({ noOpen: options.open === false })
4442
4661
  });
4443
4662
  reportBillingResult(result, `Addon: ${addonKey} \xD7${quantity}`);
4444
4663
  } catch (err) {
@@ -4492,7 +4711,7 @@ function registerBillingCommands(program2) {
4492
4711
  quantity,
4493
4712
  wait: options.wait,
4494
4713
  timeoutMs: options.timeout * 1e3,
4495
- openBrowser: browserOpener(options.open === false)
4714
+ openBrowser: paymentBrowserOpener({ noOpen: options.open === false })
4496
4715
  });
4497
4716
  succeedSpinner("Plan quantity processed.");
4498
4717
  reportBillingResult(result, `Plan quantity: ${quantity}`);
@@ -4581,7 +4800,7 @@ Purchase ${quantity}\xD7 ${colors.cyan(addonKey)}?`);
4581
4800
  quantity,
4582
4801
  wait: options.wait,
4583
4802
  timeoutMs: options.timeout * 1e3,
4584
- openBrowser: browserOpener(options.open === false)
4803
+ openBrowser: paymentBrowserOpener({ noOpen: options.open === false })
4585
4804
  });
4586
4805
  succeedSpinner("Addon purchase processed.");
4587
4806
  reportBillingResult(result, `Addon: ${addonKey} \xD7${quantity}`);
@@ -4863,16 +5082,16 @@ function formatDate4(date) {
4863
5082
 
4864
5083
  // src/lib/process.ts
4865
5084
  import { spawn } from "child_process";
4866
- import { existsSync, readFileSync } from "fs";
4867
- import { join } from "path";
5085
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
5086
+ import { join as join2 } from "path";
4868
5087
  function readPackageJson(basePath) {
4869
5088
  const base = basePath || process.cwd();
4870
- const packagePath = join(base, "package.json");
4871
- if (!existsSync(packagePath)) {
5089
+ const packagePath = join2(base, "package.json");
5090
+ if (!existsSync2(packagePath)) {
4872
5091
  return null;
4873
5092
  }
4874
5093
  try {
4875
- const content = readFileSync(packagePath, "utf-8");
5094
+ const content = readFileSync2(packagePath, "utf-8");
4876
5095
  return JSON.parse(content);
4877
5096
  } catch {
4878
5097
  return null;
@@ -4880,16 +5099,16 @@ function readPackageJson(basePath) {
4880
5099
  }
4881
5100
  function detectPackageManager(basePath) {
4882
5101
  const base = basePath || process.cwd();
4883
- if (existsSync(join(base, "bun.lockb")) || existsSync(join(base, "bun.lock"))) {
5102
+ if (existsSync2(join2(base, "bun.lockb")) || existsSync2(join2(base, "bun.lock"))) {
4884
5103
  return "bun";
4885
5104
  }
4886
- if (existsSync(join(base, "pnpm-lock.yaml"))) {
5105
+ if (existsSync2(join2(base, "pnpm-lock.yaml"))) {
4887
5106
  return "pnpm";
4888
5107
  }
4889
- if (existsSync(join(base, "yarn.lock"))) {
5108
+ if (existsSync2(join2(base, "yarn.lock"))) {
4890
5109
  return "yarn";
4891
5110
  }
4892
- if (existsSync(join(base, "package-lock.json"))) {
5111
+ if (existsSync2(join2(base, "package-lock.json"))) {
4893
5112
  return "npm";
4894
5113
  }
4895
5114
  const pkg = readPackageJson(basePath);
@@ -5044,7 +5263,7 @@ function getDefaultPort(pkg) {
5044
5263
  return framework?.defaultPort || 3e3;
5045
5264
  }
5046
5265
  function runCommand(command, env, options = {}) {
5047
- return new Promise((resolve3) => {
5266
+ return new Promise((resolve4) => {
5048
5267
  const [cmd, ...args] = parseCommand(command);
5049
5268
  const spawnOptions = {
5050
5269
  cwd: options.cwd || process.cwd(),
@@ -5079,7 +5298,7 @@ function runCommand(command, env, options = {}) {
5079
5298
  child.on("close", (code, signal) => {
5080
5299
  process.off("SIGINT", handleSignal);
5081
5300
  process.off("SIGTERM", handleSignal);
5082
- resolve3({
5301
+ resolve4({
5083
5302
  exitCode: code ?? 1,
5084
5303
  signal
5085
5304
  });
@@ -5088,7 +5307,7 @@ function runCommand(command, env, options = {}) {
5088
5307
  process.off("SIGINT", handleSignal);
5089
5308
  process.off("SIGTERM", handleSignal);
5090
5309
  console.error(`Failed to start process: ${err.message}`);
5091
- resolve3({
5310
+ resolve4({
5092
5311
  exitCode: 1,
5093
5312
  signal: null
5094
5313
  });
@@ -5277,32 +5496,32 @@ function findApp2(apps, identifier) {
5277
5496
  }
5278
5497
 
5279
5498
  // src/commands/call.ts
5280
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
5499
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
5281
5500
  import { homedir } from "os";
5282
- import { join as join2 } from "path";
5501
+ import { join as join3 } from "path";
5283
5502
  var MANIFEST_TTL_MS = 5 * 60 * 1e3;
5284
5503
  function manifestCachePath() {
5285
- return join2(homedir(), ".tarout", "surface-manifest-cache.json");
5504
+ return join3(homedir(), ".tarout", "surface-manifest-cache.json");
5286
5505
  }
5287
5506
  async function fetchManifestFresh(client, apiUrl) {
5288
5507
  const manifest = await client.settings.getSurfaceManifest.query();
5289
5508
  try {
5290
- const dir = join2(homedir(), ".tarout");
5291
- if (!existsSync2(dir)) mkdirSync(dir, { recursive: true });
5509
+ const dir = join3(homedir(), ".tarout");
5510
+ if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
5292
5511
  let cache = {};
5293
5512
  try {
5294
- cache = JSON.parse(readFileSync2(manifestCachePath(), "utf8"));
5513
+ cache = JSON.parse(readFileSync3(manifestCachePath(), "utf8"));
5295
5514
  } catch {
5296
5515
  }
5297
5516
  cache[apiUrl] = { at: Date.now(), manifest };
5298
- writeFileSync(manifestCachePath(), JSON.stringify(cache), { mode: 384 });
5517
+ writeFileSync2(manifestCachePath(), JSON.stringify(cache), { mode: 384 });
5299
5518
  } catch {
5300
5519
  }
5301
5520
  return manifest;
5302
5521
  }
5303
5522
  async function loadManifest(client, apiUrl) {
5304
5523
  try {
5305
- const cache = JSON.parse(readFileSync2(manifestCachePath(), "utf8"));
5524
+ const cache = JSON.parse(readFileSync3(manifestCachePath(), "utf8"));
5306
5525
  const entry = cache[apiUrl];
5307
5526
  if (entry && Date.now() - entry.at < MANIFEST_TTL_MS && Array.isArray(entry.manifest)) {
5308
5527
  return entry.manifest;
@@ -5362,7 +5581,7 @@ function registerCallCommand(program2) {
5362
5581
  );
5363
5582
  }
5364
5583
  let input2 = {};
5365
- const rawInput = opts.inputFile ? readFileSync2(opts.inputFile, "utf8") : opts.input;
5584
+ const rawInput = opts.inputFile ? readFileSync3(opts.inputFile, "utf8") : opts.input;
5366
5585
  if (rawInput && rawInput.trim()) {
5367
5586
  try {
5368
5587
  input2 = JSON.parse(rawInput);
@@ -5493,15 +5712,15 @@ import { spawn as spawn2 } from "child_process";
5493
5712
  import { execFile } from "child_process";
5494
5713
  import {
5495
5714
  createReadStream,
5496
- existsSync as existsSync3,
5715
+ existsSync as existsSync4,
5497
5716
  mkdtempSync,
5498
5717
  readdirSync,
5499
- readFileSync as readFileSync3,
5718
+ readFileSync as readFileSync4,
5500
5719
  rmSync,
5501
5720
  statSync
5502
5721
  } from "fs";
5503
5722
  import { tmpdir } from "os";
5504
- import { basename, dirname, join as join3 } from "path";
5723
+ import { basename, dirname, join as join4 } from "path";
5505
5724
  import { promisify } from "util";
5506
5725
  import open4 from "open";
5507
5726
 
@@ -5603,13 +5822,13 @@ function streamDeploymentLogs(deploymentId, options) {
5603
5822
  }
5604
5823
  });
5605
5824
  let buffer = "";
5606
- const done = new Promise((resolve3) => {
5825
+ const done = new Promise((resolve4) => {
5607
5826
  ws.on("close", (code, reason) => {
5608
5827
  if (buffer.length > 0) {
5609
5828
  options.onData(buffer);
5610
5829
  }
5611
5830
  options.onClose?.(code, reason.toString());
5612
- resolve3({ code, reason: reason.toString() });
5831
+ resolve4({ code, reason: reason.toString() });
5613
5832
  });
5614
5833
  });
5615
5834
  ws.on("open", () => {
@@ -5868,7 +6087,7 @@ async function confirmRegion(region) {
5868
6087
  }
5869
6088
  }
5870
6089
  function inspectCurrentProject(cwd = process.cwd()) {
5871
- const packageJson = readJsonFile(join3(cwd, "package.json"));
6090
+ const packageJson = readJsonFile(join4(cwd, "package.json"));
5872
6091
  const dependencies = new Set(
5873
6092
  Object.keys({
5874
6093
  ...packageJson?.dependencies ?? {},
@@ -5916,8 +6135,8 @@ function printProjectInspection(inspection) {
5916
6135
  }
5917
6136
  function readJsonFile(path) {
5918
6137
  try {
5919
- if (!existsSync3(path)) return null;
5920
- return JSON.parse(readFileSync3(path, "utf8"));
6138
+ if (!existsSync4(path)) return null;
6139
+ return JSON.parse(readFileSync4(path, "utf8"));
5921
6140
  } catch {
5922
6141
  return null;
5923
6142
  }
@@ -5926,7 +6145,7 @@ function readTextFile(path, maxBytes) {
5926
6145
  try {
5927
6146
  const stat = statSync(path);
5928
6147
  if (stat.size > maxBytes) return "";
5929
- return readFileSync3(path, "utf8");
6148
+ return readFileSync4(path, "utf8");
5930
6149
  } catch {
5931
6150
  return "";
5932
6151
  }
@@ -5981,7 +6200,7 @@ function collectInspectableFiles(root) {
5981
6200
  }
5982
6201
  for (const entry of entries) {
5983
6202
  if (files.length >= 400) return;
5984
- const fullPath = join3(dir, entry.name);
6203
+ const fullPath = join4(dir, entry.name);
5985
6204
  if (entry.isDirectory()) {
5986
6205
  if (!excludedDirs.has(entry.name)) walk(fullPath, depth + 1);
5987
6206
  continue;
@@ -6033,7 +6252,7 @@ function detectDatabase(dependencies, files, cwd) {
6033
6252
  }
6034
6253
  }
6035
6254
  const prismaSchema = readTextFile(
6036
- join3(cwd, "prisma", "schema.prisma"),
6255
+ join4(cwd, "prisma", "schema.prisma"),
6037
6256
  256 * 1024
6038
6257
  );
6039
6258
  if (/provider\s*=\s*"postgresql"/i.test(prismaSchema)) {
@@ -6114,15 +6333,15 @@ function detectStorage(dependencies, files) {
6114
6333
  return { detected: reasons.length > 0, reasons: uniqueReasons(reasons) };
6115
6334
  }
6116
6335
  function detectGitSource(cwd) {
6117
- const gitPath = join3(cwd, ".git");
6118
- if (!existsSync3(gitPath)) return { hasGit: false };
6119
- let configPath = join3(gitPath, "config");
6336
+ const gitPath = join4(cwd, ".git");
6337
+ if (!existsSync4(gitPath)) return { hasGit: false };
6338
+ let configPath = join4(gitPath, "config");
6120
6339
  try {
6121
6340
  const stat = statSync(gitPath);
6122
6341
  if (stat.isFile()) {
6123
6342
  const content = readTextFile(gitPath, 4096);
6124
6343
  const match = content.match(/gitdir:\s*(.+)/i);
6125
- if (match?.[1]) configPath = join3(cwd, match[1].trim(), "config");
6344
+ if (match?.[1]) configPath = join4(cwd, match[1].trim(), "config");
6126
6345
  }
6127
6346
  } catch {
6128
6347
  }
@@ -6168,7 +6387,12 @@ async function resolveDeploymentTarget(client, profile, appIdentifier, options =
6168
6387
  }
6169
6388
  const linkedProject = getProjectConfig();
6170
6389
  const linkedApp = linkedProject ? findApp3(apps, linkedProject.applicationId) ?? findApp3(apps, linkedProject.name) : void 0;
6171
- if (linkedProject && !linkedApp && !isJsonMode()) {
6390
+ if (linkedApp) {
6391
+ return reuseAppTarget(client, profile, linkedApp, {
6392
+ promptForSource: false
6393
+ });
6394
+ }
6395
+ if (linkedProject && !isJsonMode()) {
6172
6396
  log("");
6173
6397
  log(
6174
6398
  colors.warn(
@@ -6181,12 +6405,10 @@ async function resolveDeploymentTarget(client, profile, appIdentifier, options =
6181
6405
  return createNewAppTarget(client, profile, options);
6182
6406
  }
6183
6407
  if (shouldSkipConfirmation()) {
6184
- if (linkedApp) return reuseAppTarget(client, profile, linkedApp);
6185
6408
  assertConfiguredSourceAllowsCreate(sourcePreference, true);
6186
6409
  return createNewAppTarget(client, profile, options);
6187
6410
  }
6188
6411
  const createValue = "__create__";
6189
- const orderedApps = linkedApp ? [linkedApp, ...apps.filter((a) => a.applicationId !== linkedApp.applicationId)] : apps;
6190
6412
  const selected = await select(
6191
6413
  "Create a new app or reuse an existing one?",
6192
6414
  [
@@ -6194,17 +6416,16 @@ async function resolveDeploymentTarget(client, profile, appIdentifier, options =
6194
6416
  name: `Create a new app from ${colors.cyan(basename(process.cwd()) || "this directory")}`,
6195
6417
  value: createValue
6196
6418
  },
6197
- ...orderedApps.map((app2) => ({
6198
- name: `Reuse ${app2.name} ${colors.dim(`(${app2.applicationId.slice(0, 8)})`)}${linkedApp && app2.applicationId === linkedApp.applicationId ? colors.dim(" \u2014 linked") : ""}`,
6419
+ ...apps.map((app2) => ({
6420
+ name: `Reuse ${app2.name} ${colors.dim(`(${app2.applicationId.slice(0, 8)})`)}`,
6199
6421
  value: app2.applicationId
6200
6422
  }))
6201
6423
  ],
6202
6424
  {
6203
6425
  field: "deploy_app",
6204
- flag: "--new-app to create a new app, or --app <id|name> to reuse an existing one",
6426
+ flag: "--new-app to create a new app, or pass the app id or name as the positional argument (tarout deploy <id|name>) to reuse an existing one",
6205
6427
  context: {
6206
- linkedApp: linkedApp ? { id: linkedApp.applicationId, name: linkedApp.name } : null,
6207
- apps: orderedApps.map((a) => ({
6428
+ apps: apps.map((a) => ({
6208
6429
  id: a.applicationId,
6209
6430
  name: a.name
6210
6431
  }))
@@ -6236,7 +6457,7 @@ async function createNewAppTarget(client, profile, options) {
6236
6457
  shouldUploadSource: true
6237
6458
  };
6238
6459
  }
6239
- async function reuseAppTarget(client, profile, app) {
6460
+ async function reuseAppTarget(client, profile, app, opts = {}) {
6240
6461
  setProjectConfig({
6241
6462
  applicationId: app.applicationId,
6242
6463
  name: app.name,
@@ -6248,7 +6469,11 @@ async function reuseAppTarget(client, profile, app) {
6248
6469
  app,
6249
6470
  createdApp: false,
6250
6471
  hasConfiguredSource: hasConfiguredSource(details),
6251
- shouldUploadSource: await promptForLocalSourceIfNeeded(details)
6472
+ // A deterministic redeploy (linked app) must not prompt for a source
6473
+ // override — it silently reuses the app's existing source, or the current
6474
+ // folder when `--source upload` is set downstream. Only an interactive
6475
+ // menu reuse offers the override.
6476
+ shouldUploadSource: opts.promptForSource === false ? shouldUseLocalSource(details, { linkedProject: true }) : await promptForLocalSourceIfNeeded(details)
6252
6477
  };
6253
6478
  }
6254
6479
  async function createAppFromCurrentDirectory(client, profile, options = {}) {
@@ -6400,9 +6625,7 @@ async function runInlineUpgrade(client, planKey) {
6400
6625
  planKey,
6401
6626
  wait: true,
6402
6627
  timeoutMs: 6e5,
6403
- openBrowser: isJsonMode() ? void 0 : async (url) => {
6404
- await open4(url);
6405
- },
6628
+ openBrowser: paymentBrowserOpener(),
6406
6629
  onCheckoutOpened: ({ orderId, paymentUrl }) => {
6407
6630
  log("");
6408
6631
  log("Open this URL to complete payment:");
@@ -6452,9 +6675,7 @@ async function runInlineTargetedRemedy(client, remedy) {
6452
6675
  ...input2,
6453
6676
  wait: true,
6454
6677
  timeoutMs: 6e5,
6455
- openBrowser: isJsonMode() ? void 0 : async (url) => {
6456
- await open4(url);
6457
- },
6678
+ openBrowser: paymentBrowserOpener(),
6458
6679
  onCheckoutOpened: ({ orderId, paymentUrl }) => {
6459
6680
  log("");
6460
6681
  log("Open this URL to complete payment:");
@@ -6651,7 +6872,8 @@ async function emitNeedsUpgrade(client, err, requestedPlan, retryCommand) {
6651
6872
  suggestedTarget: remedy.targetKey,
6652
6873
  nextCommand: remedy.command,
6653
6874
  options,
6654
- hint
6875
+ hint,
6876
+ permissionHint: AGENT_BILLING_PERMISSION_HINT
6655
6877
  });
6656
6878
  }
6657
6879
  async function listFreeDatabasesSafely(client) {
@@ -7725,8 +7947,8 @@ async function uploadCurrentDirectorySource(client, applicationId, appName) {
7725
7947
  }
7726
7948
  }
7727
7949
  async function createSourceArchive() {
7728
- const tempDir = mkdtempSync(join3(tmpdir(), "tarout-source-"));
7729
- const archivePath = join3(tempDir, "source.zip");
7950
+ const tempDir = mkdtempSync(join4(tmpdir(), "tarout-source-"));
7951
+ const archivePath = join4(tempDir, "source.zip");
7730
7952
  const excludes = [
7731
7953
  ".git/*",
7732
7954
  ".tarout/*",
@@ -8231,7 +8453,7 @@ function registerDeployCommands(program2) {
8231
8453
  name: `${colors.cyan(d.deploymentId.slice(0, 8))} - ${d.title || "Deployment"} (${formatDate5(d.createdAt)})${index === 0 ? colors.dim(" [current]") : ""}`,
8232
8454
  value: d.deploymentId
8233
8455
  }));
8234
- const { select: select2 } = await import("./prompts-JH6YBHHV.js");
8456
+ const { select: select2 } = await import("./prompts-D72VJYWE.js");
8235
8457
  targetDeploymentId = await select2(
8236
8458
  "Select deployment:",
8237
8459
  choices,
@@ -8250,7 +8472,7 @@ function registerDeployCommands(program2) {
8250
8472
  ` Created: ${targetDeployment ? formatDate5(targetDeployment.createdAt) : colors.dim("unknown")}`
8251
8473
  );
8252
8474
  log("");
8253
- const { confirm: confirm2 } = await import("./prompts-JH6YBHHV.js");
8475
+ const { confirm: confirm2 } = await import("./prompts-D72VJYWE.js");
8254
8476
  const confirmed = await confirm2(
8255
8477
  "Are you sure you want to rollback?",
8256
8478
  false,
@@ -8382,7 +8604,7 @@ function formatDate5(date) {
8382
8604
  });
8383
8605
  }
8384
8606
  function sleep(ms) {
8385
- return new Promise((resolve3) => setTimeout(resolve3, ms));
8607
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
8386
8608
  }
8387
8609
  async function streamDeploymentWithLogs(client, deploymentId, appName, applicationId) {
8388
8610
  stopSpinner();
@@ -11953,7 +12175,7 @@ function truncate(str, max) {
11953
12175
  }
11954
12176
 
11955
12177
  // src/commands/env.ts
11956
- import { chmodSync, existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
12178
+ import { chmodSync, existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
11957
12179
  function registerEnvCommands(program2) {
11958
12180
  const env = program2.command("env").argument("<app>", "Application ID or name").description("Manage environment variables");
11959
12181
  env.command("list").alias("ls").description("List all environment variables").option("--reveal", "Show actual values (not masked)").action(async (options, command) => {
@@ -12113,7 +12335,7 @@ function registerEnvCommands(program2) {
12113
12335
  );
12114
12336
  throw new NotFoundError("Application", appIdentifier, suggestions);
12115
12337
  }
12116
- if (existsSync4(options.output) && !shouldSkipConfirmation()) {
12338
+ if (existsSync5(options.output) && !shouldSkipConfirmation()) {
12117
12339
  succeedSpinner();
12118
12340
  const confirmed = await confirm(
12119
12341
  `File ${options.output} already exists. Overwrite?`,
@@ -12134,7 +12356,7 @@ function registerEnvCommands(program2) {
12134
12356
  format: "dotenv",
12135
12357
  maskSecrets: !options.reveal
12136
12358
  });
12137
- writeFileSync2(options.output, result.content, { mode: 384 });
12359
+ writeFileSync3(options.output, result.content, { mode: 384 });
12138
12360
  try {
12139
12361
  chmodSync(options.output, 384);
12140
12362
  } catch {
@@ -12153,10 +12375,10 @@ function registerEnvCommands(program2) {
12153
12375
  try {
12154
12376
  if (!isLoggedIn()) throw new AuthError();
12155
12377
  const appIdentifier = command.parent.parent.args[0];
12156
- if (!existsSync4(options.input)) {
12378
+ if (!existsSync5(options.input)) {
12157
12379
  throw new InvalidArgumentError(`File not found: ${options.input}`);
12158
12380
  }
12159
- const content = readFileSync4(options.input, "utf-8");
12381
+ const content = readFileSync5(options.input, "utf-8");
12160
12382
  const client = getApiClient();
12161
12383
  const _spinner = startSpinner("Uploading environment variables...");
12162
12384
  const apps = await client.application.allByOrganization.query();
@@ -12380,7 +12602,7 @@ ${colors.bold(key)}: ${maskValue(val.value || String(v))}
12380
12602
  } else {
12381
12603
  const _raw = await import("process");
12382
12604
  log('Enter JSON key-value object (e.g. {"KEY":"value"}):');
12383
- const input2 = await (await import("./prompts-JH6YBHHV.js")).input(
12605
+ const input2 = await (await import("./prompts-D72VJYWE.js")).input(
12384
12606
  "JSON:"
12385
12607
  );
12386
12608
  vars = JSON.parse(input2);
@@ -12403,7 +12625,7 @@ ${colors.bold(key)}: ${maskValue(val.value || String(v))}
12403
12625
  try {
12404
12626
  if (!isLoggedIn()) throw new AuthError();
12405
12627
  if (!shouldSkipConfirmation()) {
12406
- const { confirm: confirmFn } = await import("./prompts-JH6YBHHV.js");
12628
+ const { confirm: confirmFn } = await import("./prompts-D72VJYWE.js");
12407
12629
  const ok = await confirmFn(
12408
12630
  `Delete ${keys.length} variable(s)?`,
12409
12631
  false
@@ -12442,7 +12664,7 @@ ${colors.bold(key)}: ${maskValue(val.value || String(v))}
12442
12664
  );
12443
12665
  if (!app) throw new NotFoundError("Application", appIdentifier);
12444
12666
  if (!shouldSkipConfirmation()) {
12445
- const { confirm: confirmFn } = await import("./prompts-JH6YBHHV.js");
12667
+ const { confirm: confirmFn } = await import("./prompts-D72VJYWE.js");
12446
12668
  const ok = await confirmFn(
12447
12669
  `Copy env vars from ${fromEnvId} to ${toEnvId}?`,
12448
12670
  false
@@ -12890,8 +13112,8 @@ function registerInboxCommands(program2) {
12890
13112
  }
12891
13113
 
12892
13114
  // src/commands/init.ts
12893
- import { existsSync as existsSync5, writeFileSync as writeFileSync3 } from "fs";
12894
- import { basename as basename2, join as join4, resolve } from "path";
13115
+ import { existsSync as existsSync6, writeFileSync as writeFileSync4 } from "fs";
13116
+ import { basename as basename2, join as join5, resolve as resolve2 } from "path";
12895
13117
  var DEFAULT_REGION2 = "me-central2";
12896
13118
  var STARTER_INDEX_JS = `import { createServer } from "node:http";
12897
13119
 
@@ -12909,8 +13131,8 @@ function toPackageName(name) {
12909
13131
  }
12910
13132
  function scaffoldStarter(cwd) {
12911
13133
  const written = [];
12912
- const pkgPath = join4(cwd, "package.json");
12913
- if (existsSync5(pkgPath)) return written;
13134
+ const pkgPath = join5(cwd, "package.json");
13135
+ if (existsSync6(pkgPath)) return written;
12914
13136
  const pkg = {
12915
13137
  name: toPackageName(basename2(cwd) || "tarout-app"),
12916
13138
  version: "0.1.0",
@@ -12918,12 +13140,12 @@ function scaffoldStarter(cwd) {
12918
13140
  type: "module",
12919
13141
  scripts: { start: "node index.js", dev: "node index.js" }
12920
13142
  };
12921
- writeFileSync3(pkgPath, `${JSON.stringify(pkg, null, 2)}
13143
+ writeFileSync4(pkgPath, `${JSON.stringify(pkg, null, 2)}
12922
13144
  `);
12923
13145
  written.push("package.json");
12924
- const indexPath = join4(cwd, "index.js");
12925
- if (!existsSync5(indexPath)) {
12926
- writeFileSync3(indexPath, STARTER_INDEX_JS);
13146
+ const indexPath = join5(cwd, "index.js");
13147
+ if (!existsSync6(indexPath)) {
13148
+ writeFileSync4(indexPath, STARTER_INDEX_JS);
12927
13149
  written.push("index.js");
12928
13150
  }
12929
13151
  return written;
@@ -12935,9 +13157,9 @@ function writeEnvFile(cwd, vars) {
12935
13157
  const lines = Object.entries(vars).map(
12936
13158
  ([k, v]) => `${k}=${envValueNeedsQuote(v) ? JSON.stringify(v) : v}`
12937
13159
  );
12938
- const primary = join4(cwd, ".env");
12939
- const target = existsSync5(primary) ? join4(cwd, ".env.tarout") : primary;
12940
- writeFileSync3(target, `${lines.join("\n")}
13160
+ const primary = join5(cwd, ".env");
13161
+ const target = existsSync6(primary) ? join5(cwd, ".env.tarout") : primary;
13162
+ writeFileSync4(target, `${lines.join("\n")}
12941
13163
  `, { mode: 384 });
12942
13164
  return target;
12943
13165
  }
@@ -12956,7 +13178,7 @@ function registerInitCommand(program2) {
12956
13178
  "Provision a database: none, postgres, or mysql (defaults to auto-detected)"
12957
13179
  ).option("--database-plan <plan>", "Database plan (e.g. free, starter)").option("--storage", "Provision file storage").option("--storage-plan <plan>", "Storage plan (e.g. free, starter)").option("--scaffold", "Write a minimal starter app if the directory is empty").option("--no-env-write", "Do not write a local .env file with connection strings").action(async (cwdArg, options) => {
12958
13180
  try {
12959
- const cwd = cwdArg ? resolve(cwdArg) : process.cwd();
13181
+ const cwd = cwdArg ? resolve2(cwdArg) : process.cwd();
12960
13182
  if (cwdArg) process.chdir(cwd);
12961
13183
  if (options.scaffold) {
12962
13184
  const files = scaffoldStarter(cwd);
@@ -13019,7 +13241,8 @@ function registerInitCommand(program2) {
13019
13241
  outputError("NEEDS_UPGRADE", message, {
13020
13242
  suggestedPlan: inferSuggestedPlan(options.plan),
13021
13243
  failedEntitlementKey: extractEntitlementKeyFromError(err),
13022
- hint: "Run `tarout billing upgrade <plan> --wait` to add slots, then retry `tarout init`."
13244
+ hint: "Run `tarout billing upgrade <plan> --wait` to add slots, then retry `tarout init`.",
13245
+ permissionHint: AGENT_BILLING_PERMISSION_HINT
13023
13246
  });
13024
13247
  exit(ExitCode.PERMISSION_DENIED);
13025
13248
  }
@@ -13156,8 +13379,8 @@ function registerKeysCommands(program2) {
13156
13379
  });
13157
13380
  }
13158
13381
  if (!publicKey && options.file) {
13159
- const { readFileSync: readFileSync5 } = await import("fs");
13160
- publicKey = readFileSync5(options.file, "utf-8").trim();
13382
+ const { readFileSync: readFileSync6 } = await import("fs");
13383
+ publicKey = readFileSync6(options.file, "utf-8").trim();
13161
13384
  }
13162
13385
  if (!publicKey) {
13163
13386
  log(
@@ -19345,7 +19568,7 @@ function truncate3(str, max) {
19345
19568
  }
19346
19569
 
19347
19570
  // src/commands/up.ts
19348
- import { resolve as resolve2 } from "path";
19571
+ import { resolve as resolve3 } from "path";
19349
19572
  var DEFAULT_REGION3 = "me-central2";
19350
19573
  function normalizeSource(value) {
19351
19574
  if (!value) return "upload";
@@ -19404,7 +19627,7 @@ function registerUpCommand(program2) {
19404
19627
  "Idempotency key for safe retries (Phase 2; logged only in v1)"
19405
19628
  ).action(async (cwdArg, options) => {
19406
19629
  try {
19407
- const cwd = cwdArg ? resolve2(cwdArg) : process.cwd();
19630
+ const cwd = cwdArg ? resolve3(cwdArg) : process.cwd();
19408
19631
  if (cwdArg) process.chdir(cwd);
19409
19632
  const source = normalizeSource(options.source);
19410
19633
  const idempotencyKey = options.idempotencyKey?.trim();
@@ -19921,6 +20144,7 @@ registerAuthCommands(program);
19921
20144
  registerAppsCommands(program);
19922
20145
  registerDeployCommands(program);
19923
20146
  registerInitCommand(program);
20147
+ registerAgentCommands(program);
19924
20148
  registerUpCommand(program);
19925
20149
  registerLogsCommand(program);
19926
20150
  registerEnvCommands(program);
@@ -4,8 +4,8 @@ import {
4
4
  password,
5
5
  promptOrEmit,
6
6
  select
7
- } from "./chunk-DI66W4S5.js";
8
- import "./chunk-K7JK5HIL.js";
7
+ } from "./chunk-OPPZGNJX.js";
8
+ import "./chunk-WKITMZ4U.js";
9
9
  export {
10
10
  confirm,
11
11
  input,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tarout/cli",
3
- "version": "0.7.2",
3
+ "version": "0.9.0",
4
4
  "description": "Tarout CLI — the Saudi cloud platform for coding agents",
5
5
  "type": "module",
6
6
  "bin": {