@tarout/cli 0.7.2 → 0.10.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.10.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,205 @@ 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 hasTaroutAllowlist(cwd) {
791
+ const settingsPath = join(cwd, ".claude", "settings.local.json");
792
+ if (!existsSync(settingsPath)) return false;
793
+ try {
794
+ const settings = JSON.parse(
795
+ readFileSync(settingsPath, "utf-8")
796
+ );
797
+ const allow = settings?.permissions?.allow;
798
+ return Array.isArray(allow) && allow.includes(TAROUT_ALLOW_ENTRY);
799
+ } catch {
800
+ return false;
801
+ }
802
+ }
803
+ function mergeClaudeSettings(claudeDir) {
804
+ const settingsPath = join(claudeDir, "settings.local.json");
805
+ const relPath = join(".claude", "settings.local.json");
806
+ if (!existsSync(settingsPath)) {
807
+ mkdirSync(claudeDir, { recursive: true });
808
+ const settings2 = {
809
+ permissions: { allow: [TAROUT_ALLOW_ENTRY] }
810
+ };
811
+ writeFileSync(settingsPath, `${JSON.stringify(settings2, null, 2)}
812
+ `, "utf-8");
813
+ return { path: relPath, action: "created" };
814
+ }
815
+ let settings;
816
+ try {
817
+ settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
818
+ } catch {
819
+ return {
820
+ path: relPath,
821
+ action: "skipped",
822
+ reason: "existing settings.local.json is not valid JSON; left untouched"
823
+ };
824
+ }
825
+ if (typeof settings !== "object" || settings === null) {
826
+ return {
827
+ path: relPath,
828
+ action: "skipped",
829
+ reason: "existing settings.local.json is not a JSON object; left untouched"
830
+ };
831
+ }
832
+ const permissions = settings.permissions ??= {};
833
+ const allow = Array.isArray(permissions.allow) ? permissions.allow : [];
834
+ if (allow.includes(TAROUT_ALLOW_ENTRY)) {
835
+ return { path: relPath, action: "unchanged" };
836
+ }
837
+ permissions.allow = [...allow, TAROUT_ALLOW_ENTRY];
838
+ writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
839
+ `, "utf-8");
840
+ return { path: relPath, action: "updated" };
841
+ }
842
+ function scaffoldAgentConfig(options) {
843
+ const { cwd, agent } = options;
844
+ const files = [];
845
+ const mdName = markdownTargetFor(agent);
846
+ files.push({
847
+ path: mdName,
848
+ action: upsertMarkdownBlock(join(cwd, mdName), TAROUT_AGENT_BLOCK)
849
+ });
850
+ if (agent === "claude") {
851
+ files.push(mergeClaudeSettings(join(cwd, ".claude")));
852
+ }
853
+ return {
854
+ agent,
855
+ files,
856
+ nextSteps: ["tarout up --json --yes"]
857
+ };
858
+ }
859
+
860
+ // src/commands/agent.ts
861
+ function parseAgent(value) {
862
+ const agent = (value ?? "claude").toLowerCase();
863
+ if (AGENT_TYPES.includes(agent)) {
864
+ return agent;
865
+ }
866
+ throw new CliError(
867
+ `Invalid agent "${value}". Use one of: ${AGENT_TYPES.join(", ")}.`,
868
+ ExitCode.INVALID_ARGUMENTS
869
+ );
870
+ }
871
+ function registerAgentCommands(program2) {
872
+ const agent = program2.command("agent").description("Configure coding agents to use the Tarout CLI");
873
+ agent.command("init").argument("[path]", "Project directory (defaults to current)").description(
874
+ "Write agent instructions (CLAUDE.md/AGENTS.md) and a Bash(tarout:*) permission allowlist so a coding agent can drive Tarout"
875
+ ).option(
876
+ "--agent <type>",
877
+ `Coding agent to configure: ${AGENT_TYPES.join(", ")}`,
878
+ "claude"
879
+ ).action(async (cwdArg, options) => {
880
+ try {
881
+ const cwd = cwdArg ? resolve(cwdArg) : process.cwd();
882
+ const agentType = parseAgent(options.agent);
883
+ const result = scaffoldAgentConfig({ cwd, agent: agentType });
884
+ if (isJsonMode()) {
885
+ for (const file of result.files) {
886
+ outputJsonLine({
887
+ type: "event",
888
+ event: "file_written",
889
+ path: file.path,
890
+ action: file.action,
891
+ ...file.reason ? { reason: file.reason } : {}
892
+ });
893
+ }
894
+ outputJsonLine({
895
+ type: "result",
896
+ ok: true,
897
+ agent: result.agent,
898
+ files: result.files,
899
+ nextSteps: result.nextSteps
900
+ });
901
+ return;
902
+ }
903
+ success("Coding agents configured");
904
+ box(
905
+ `Agent: ${colors.cyan(result.agent)}`,
906
+ result.files.map((file) => {
907
+ const label = `${colors.bold(file.action)} ${file.path}`;
908
+ return file.reason ? `${label} \u2014 ${colors.dim(file.reason)}` : label;
909
+ })
910
+ );
911
+ for (const file of result.files) {
912
+ if (file.action === "skipped") {
913
+ warn(`${file.path} was left untouched (${file.reason}).`);
914
+ }
915
+ }
916
+ log("Next steps:");
917
+ for (const step of result.nextSteps) {
918
+ log(` ${colors.dim(step)}`);
919
+ }
920
+ log("");
921
+ } catch (err) {
922
+ handleError(err);
923
+ }
924
+ });
925
+ }
926
+
727
927
  // src/commands/ai.ts
728
928
  function registerAiCommands(program2) {
729
929
  const ai = program2.command("ai").description("Manage AI Gateway models and API keys");
@@ -1544,7 +1744,7 @@ function registerAppsCommands(program2) {
1544
1744
  throw new NotFoundError("Application", appIdentifier, suggestions);
1545
1745
  }
1546
1746
  if (!shouldSkipConfirmation()) {
1547
- const { confirm: confirm2 } = await import("./prompts-JH6YBHHV.js");
1747
+ const { confirm: confirm2 } = await import("./prompts-D72VJYWE.js");
1548
1748
  const confirmed = await confirm2(
1549
1749
  `Stop application "${app.name}"?`,
1550
1750
  false,
@@ -1862,7 +2062,7 @@ function registerAppsCommands(program2) {
1862
2062
  throw new NotFoundError("Application", appIdentifier);
1863
2063
  }
1864
2064
  if (!shouldSkipConfirmation()) {
1865
- const { confirm: confirm2 } = await import("./prompts-JH6YBHHV.js");
2065
+ const { confirm: confirm2 } = await import("./prompts-D72VJYWE.js");
1866
2066
  const confirmed = await confirm2(
1867
2067
  `Disconnect source provider from "${app.name}"?`,
1868
2068
  false,
@@ -2880,7 +3080,7 @@ function formatTime(ts) {
2880
3080
  }
2881
3081
 
2882
3082
  // src/commands/auth.ts
2883
- import open2 from "open";
3083
+ import open3 from "open";
2884
3084
 
2885
3085
  // src/lib/auth-server.ts
2886
3086
  import { randomBytes, timingSafeEqual } from "crypto";
@@ -2898,7 +3098,7 @@ function escapeHtml(value) {
2898
3098
  return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
2899
3099
  }
2900
3100
  function startAuthServer(options = {}) {
2901
- return new Promise((resolve3) => {
3101
+ return new Promise((resolve4) => {
2902
3102
  const app = express();
2903
3103
  const expectedState = options.state ?? createAuthState();
2904
3104
  let server;
@@ -3022,7 +3222,7 @@ function startAuthServer(options = {}) {
3022
3222
  server = app.listen(0, "127.0.0.1", () => {
3023
3223
  const address = server.address();
3024
3224
  const port = typeof address === "object" && address ? address.port : 0;
3025
- resolve3({
3225
+ resolve4({
3026
3226
  port,
3027
3227
  state: expectedState,
3028
3228
  waitForCallback: () => callbackPromise,
@@ -3032,6 +3232,28 @@ function startAuthServer(options = {}) {
3032
3232
  });
3033
3233
  }
3034
3234
 
3235
+ // src/lib/browser.ts
3236
+ import open2 from "open";
3237
+ function canLaunchBrowser() {
3238
+ if (process.platform === "darwin" || process.platform === "win32") {
3239
+ return true;
3240
+ }
3241
+ return Boolean(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
3242
+ }
3243
+ function paymentBrowserOpener(opts) {
3244
+ if (opts?.noOpen || !canLaunchBrowser()) return void 0;
3245
+ return async (url) => {
3246
+ try {
3247
+ await open2(url);
3248
+ } catch {
3249
+ }
3250
+ };
3251
+ }
3252
+ function shouldAutoConfirmPaidCheckout(amountDueHalalas) {
3253
+ const isPaidCheckout = typeof amountDueHalalas === "number" && amountDueHalalas > 0;
3254
+ return !isJsonMode() && isNonInteractiveMode() && canLaunchBrowser() && isPaidCheckout;
3255
+ }
3256
+
3035
3257
  // src/commands/auth.ts
3036
3258
  function refuseBrowserAuthForAgent(action) {
3037
3259
  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 +3292,7 @@ function registerAuthCommands(program2) {
3070
3292
  return;
3071
3293
  }
3072
3294
  }
3073
- if (isJsonMode() || isNonInteractiveMode()) {
3295
+ if (isJsonMode() || !canLaunchBrowser()) {
3074
3296
  refuseBrowserAuthForAgent("login");
3075
3297
  }
3076
3298
  const apiUrl = options.apiUrl;
@@ -3079,7 +3301,12 @@ function registerAuthCommands(program2) {
3079
3301
  const authServer = await startAuthServer();
3080
3302
  const callbackUrl = `http://localhost:${authServer.port}/callback?state=${encodeURIComponent(authServer.state)}`;
3081
3303
  const authUrl = `${apiUrl}/cli-authorize?callback=${encodeURIComponent(callbackUrl)}`;
3082
- await open2(authUrl);
3304
+ try {
3305
+ await open3(authUrl);
3306
+ } catch {
3307
+ authServer.close();
3308
+ refuseBrowserAuthForAgent("login");
3309
+ }
3083
3310
  const _spinner = startSpinner("Waiting for authentication...");
3084
3311
  try {
3085
3312
  const authData = await authServer.waitForCallback();
@@ -3179,7 +3406,7 @@ function registerAuthCommands(program2) {
3179
3406
  return;
3180
3407
  }
3181
3408
  }
3182
- if (isJsonMode() || isNonInteractiveMode()) {
3409
+ if (isJsonMode() || !canLaunchBrowser()) {
3183
3410
  refuseBrowserAuthForAgent("register");
3184
3411
  }
3185
3412
  const apiUrl = options.apiUrl;
@@ -3188,7 +3415,12 @@ function registerAuthCommands(program2) {
3188
3415
  const authServer = await startAuthServer();
3189
3416
  const callbackUrl = `http://localhost:${authServer.port}/callback?state=${encodeURIComponent(authServer.state)}`;
3190
3417
  const authUrl = `${apiUrl}/cli-authorize?action=register&callback=${encodeURIComponent(callbackUrl)}`;
3191
- await open2(authUrl);
3418
+ try {
3419
+ await open3(authUrl);
3420
+ } catch {
3421
+ authServer.close();
3422
+ refuseBrowserAuthForAgent("register");
3423
+ }
3192
3424
  const _spinner = startSpinner("Waiting for account creation...");
3193
3425
  try {
3194
3426
  const authData = await authServer.waitForCallback();
@@ -3303,7 +3535,7 @@ function registerAuthCommands(program2) {
3303
3535
  () => input("Token name (e.g., ci-deploy):")
3304
3536
  );
3305
3537
  }
3306
- const { getApiClient: getApiClient2 } = await import("./api-QVUO7EWV.js");
3538
+ const { getApiClient: getApiClient2 } = await import("./api-YFXACHOB.js");
3307
3539
  const client = getApiClient2();
3308
3540
  const profile = getCurrentProfile();
3309
3541
  let keyOrg = profile?.organizationId;
@@ -3680,7 +3912,7 @@ function registerBackupsCommands(program2) {
3680
3912
  try {
3681
3913
  if (!isLoggedIn()) throw new AuthError();
3682
3914
  if (!shouldSkipConfirmation()) {
3683
- const { confirm: confirmFn } = await import("./prompts-JH6YBHHV.js");
3915
+ const { confirm: confirmFn } = await import("./prompts-D72VJYWE.js");
3684
3916
  const ok = await confirmFn(
3685
3917
  `Restore backup file "${backupFile}"? This will overwrite current data.`,
3686
3918
  false
@@ -3729,9 +3961,9 @@ function formatBytes2(bytes) {
3729
3961
 
3730
3962
  // src/commands/billing.ts
3731
3963
  import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
3732
- import open3 from "open";
3733
3964
 
3734
3965
  // src/lib/billing-upgrade.ts
3966
+ 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
3967
  function resolveTarget(input2) {
3736
3968
  if (input2.kind === "plan") return input2.planKey ?? "";
3737
3969
  if (input2.kind === "addon") {
@@ -3797,6 +4029,13 @@ async function finalizeBillingMutation(client, result, ctx) {
3797
4029
  }
3798
4030
  const orderId = result.orderId;
3799
4031
  const paymentUrl = result.paymentUrl;
4032
+ if (ctx.wait) ctx.onCheckoutOpened?.({ orderId, paymentUrl });
4033
+ if (ctx.openBrowser) {
4034
+ try {
4035
+ await ctx.openBrowser(paymentUrl);
4036
+ } catch {
4037
+ }
4038
+ }
3800
4039
  if (!ctx.wait) {
3801
4040
  return {
3802
4041
  status: "payment_required",
@@ -3807,13 +4046,6 @@ async function finalizeBillingMutation(client, result, ctx) {
3807
4046
  amountHalalas
3808
4047
  };
3809
4048
  }
3810
- ctx.onCheckoutOpened?.({ orderId, paymentUrl });
3811
- if (ctx.openBrowser) {
3812
- try {
3813
- await ctx.openBrowser(paymentUrl);
3814
- } catch {
3815
- }
3816
- }
3817
4049
  const final = await pollCheckoutUntilTerminal(client, orderId, {
3818
4050
  timeoutMs: ctx.timeoutMs ?? 6e5,
3819
4051
  intervalMs: 4e3
@@ -3994,12 +4226,6 @@ function reportBillingResult(result, label) {
3994
4226
  const code = emitBillingResult(result, { label });
3995
4227
  if (code !== ExitCode.SUCCESS) exit(code);
3996
4228
  }
3997
- function browserOpener(noOpen) {
3998
- if (isJsonMode() || noOpen) return void 0;
3999
- return async (url) => {
4000
- await open3(url);
4001
- };
4002
- }
4003
4229
  function isConflictError(err) {
4004
4230
  const e = err;
4005
4231
  return (e?.code ?? e?.data?.code) === "CONFLICT";
@@ -4205,24 +4431,30 @@ function registerBillingCommands(program2) {
4205
4431
  );
4206
4432
  }
4207
4433
  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
4434
+ if (shouldAutoConfirmPaidCheckout(amountDueHalalas)) {
4435
+ log(
4436
+ "Opening the secure payment page in your browser to complete the upgrade..."
4437
+ );
4438
+ } else {
4439
+ const confirmed = await confirm(
4440
+ `Switch to plan "${targetPlan}"?`,
4441
+ false,
4442
+ {
4443
+ field: "confirm_upgrade",
4444
+ flag: "--yes",
4445
+ context: {
4446
+ plan: targetPlan,
4447
+ quantity: planQuantity,
4448
+ billingPeriod,
4449
+ addons,
4450
+ amountDueHalalas
4451
+ }
4220
4452
  }
4453
+ );
4454
+ if (!confirmed) {
4455
+ log("Cancelled.");
4456
+ return;
4221
4457
  }
4222
- );
4223
- if (!confirmed) {
4224
- log("Cancelled.");
4225
- return;
4226
4458
  }
4227
4459
  }
4228
4460
  const _changeSpinner = startSpinner("Changing plan...");
@@ -4234,7 +4466,7 @@ function registerBillingCommands(program2) {
4234
4466
  addons,
4235
4467
  wait: options.wait,
4236
4468
  timeoutMs: options.timeout * 1e3,
4237
- openBrowser: browserOpener(options.open === false),
4469
+ openBrowser: paymentBrowserOpener({ noOpen: options.open === false }),
4238
4470
  onCheckoutOpened: ({ orderId, paymentUrl }) => {
4239
4471
  if (isJsonMode()) {
4240
4472
  outputJsonLine({
@@ -4438,7 +4670,7 @@ function registerBillingCommands(program2) {
4438
4670
  target: addonKey,
4439
4671
  wait: options.wait,
4440
4672
  timeoutMs: options.timeout * 1e3,
4441
- openBrowser: browserOpener(options.open === false)
4673
+ openBrowser: paymentBrowserOpener({ noOpen: options.open === false })
4442
4674
  });
4443
4675
  reportBillingResult(result, `Addon: ${addonKey} \xD7${quantity}`);
4444
4676
  } catch (err) {
@@ -4492,7 +4724,7 @@ function registerBillingCommands(program2) {
4492
4724
  quantity,
4493
4725
  wait: options.wait,
4494
4726
  timeoutMs: options.timeout * 1e3,
4495
- openBrowser: browserOpener(options.open === false)
4727
+ openBrowser: paymentBrowserOpener({ noOpen: options.open === false })
4496
4728
  });
4497
4729
  succeedSpinner("Plan quantity processed.");
4498
4730
  reportBillingResult(result, `Plan quantity: ${quantity}`);
@@ -4581,7 +4813,7 @@ Purchase ${quantity}\xD7 ${colors.cyan(addonKey)}?`);
4581
4813
  quantity,
4582
4814
  wait: options.wait,
4583
4815
  timeoutMs: options.timeout * 1e3,
4584
- openBrowser: browserOpener(options.open === false)
4816
+ openBrowser: paymentBrowserOpener({ noOpen: options.open === false })
4585
4817
  });
4586
4818
  succeedSpinner("Addon purchase processed.");
4587
4819
  reportBillingResult(result, `Addon: ${addonKey} \xD7${quantity}`);
@@ -4863,16 +5095,16 @@ function formatDate4(date) {
4863
5095
 
4864
5096
  // src/lib/process.ts
4865
5097
  import { spawn } from "child_process";
4866
- import { existsSync, readFileSync } from "fs";
4867
- import { join } from "path";
5098
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
5099
+ import { join as join2 } from "path";
4868
5100
  function readPackageJson(basePath) {
4869
5101
  const base = basePath || process.cwd();
4870
- const packagePath = join(base, "package.json");
4871
- if (!existsSync(packagePath)) {
5102
+ const packagePath = join2(base, "package.json");
5103
+ if (!existsSync2(packagePath)) {
4872
5104
  return null;
4873
5105
  }
4874
5106
  try {
4875
- const content = readFileSync(packagePath, "utf-8");
5107
+ const content = readFileSync2(packagePath, "utf-8");
4876
5108
  return JSON.parse(content);
4877
5109
  } catch {
4878
5110
  return null;
@@ -4880,16 +5112,16 @@ function readPackageJson(basePath) {
4880
5112
  }
4881
5113
  function detectPackageManager(basePath) {
4882
5114
  const base = basePath || process.cwd();
4883
- if (existsSync(join(base, "bun.lockb")) || existsSync(join(base, "bun.lock"))) {
5115
+ if (existsSync2(join2(base, "bun.lockb")) || existsSync2(join2(base, "bun.lock"))) {
4884
5116
  return "bun";
4885
5117
  }
4886
- if (existsSync(join(base, "pnpm-lock.yaml"))) {
5118
+ if (existsSync2(join2(base, "pnpm-lock.yaml"))) {
4887
5119
  return "pnpm";
4888
5120
  }
4889
- if (existsSync(join(base, "yarn.lock"))) {
5121
+ if (existsSync2(join2(base, "yarn.lock"))) {
4890
5122
  return "yarn";
4891
5123
  }
4892
- if (existsSync(join(base, "package-lock.json"))) {
5124
+ if (existsSync2(join2(base, "package-lock.json"))) {
4893
5125
  return "npm";
4894
5126
  }
4895
5127
  const pkg = readPackageJson(basePath);
@@ -5044,7 +5276,7 @@ function getDefaultPort(pkg) {
5044
5276
  return framework?.defaultPort || 3e3;
5045
5277
  }
5046
5278
  function runCommand(command, env, options = {}) {
5047
- return new Promise((resolve3) => {
5279
+ return new Promise((resolve4) => {
5048
5280
  const [cmd, ...args] = parseCommand(command);
5049
5281
  const spawnOptions = {
5050
5282
  cwd: options.cwd || process.cwd(),
@@ -5079,7 +5311,7 @@ function runCommand(command, env, options = {}) {
5079
5311
  child.on("close", (code, signal) => {
5080
5312
  process.off("SIGINT", handleSignal);
5081
5313
  process.off("SIGTERM", handleSignal);
5082
- resolve3({
5314
+ resolve4({
5083
5315
  exitCode: code ?? 1,
5084
5316
  signal
5085
5317
  });
@@ -5088,7 +5320,7 @@ function runCommand(command, env, options = {}) {
5088
5320
  process.off("SIGINT", handleSignal);
5089
5321
  process.off("SIGTERM", handleSignal);
5090
5322
  console.error(`Failed to start process: ${err.message}`);
5091
- resolve3({
5323
+ resolve4({
5092
5324
  exitCode: 1,
5093
5325
  signal: null
5094
5326
  });
@@ -5277,32 +5509,32 @@ function findApp2(apps, identifier) {
5277
5509
  }
5278
5510
 
5279
5511
  // src/commands/call.ts
5280
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
5512
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
5281
5513
  import { homedir } from "os";
5282
- import { join as join2 } from "path";
5514
+ import { join as join3 } from "path";
5283
5515
  var MANIFEST_TTL_MS = 5 * 60 * 1e3;
5284
5516
  function manifestCachePath() {
5285
- return join2(homedir(), ".tarout", "surface-manifest-cache.json");
5517
+ return join3(homedir(), ".tarout", "surface-manifest-cache.json");
5286
5518
  }
5287
5519
  async function fetchManifestFresh(client, apiUrl) {
5288
5520
  const manifest = await client.settings.getSurfaceManifest.query();
5289
5521
  try {
5290
- const dir = join2(homedir(), ".tarout");
5291
- if (!existsSync2(dir)) mkdirSync(dir, { recursive: true });
5522
+ const dir = join3(homedir(), ".tarout");
5523
+ if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
5292
5524
  let cache = {};
5293
5525
  try {
5294
- cache = JSON.parse(readFileSync2(manifestCachePath(), "utf8"));
5526
+ cache = JSON.parse(readFileSync3(manifestCachePath(), "utf8"));
5295
5527
  } catch {
5296
5528
  }
5297
5529
  cache[apiUrl] = { at: Date.now(), manifest };
5298
- writeFileSync(manifestCachePath(), JSON.stringify(cache), { mode: 384 });
5530
+ writeFileSync2(manifestCachePath(), JSON.stringify(cache), { mode: 384 });
5299
5531
  } catch {
5300
5532
  }
5301
5533
  return manifest;
5302
5534
  }
5303
5535
  async function loadManifest(client, apiUrl) {
5304
5536
  try {
5305
- const cache = JSON.parse(readFileSync2(manifestCachePath(), "utf8"));
5537
+ const cache = JSON.parse(readFileSync3(manifestCachePath(), "utf8"));
5306
5538
  const entry = cache[apiUrl];
5307
5539
  if (entry && Date.now() - entry.at < MANIFEST_TTL_MS && Array.isArray(entry.manifest)) {
5308
5540
  return entry.manifest;
@@ -5362,7 +5594,7 @@ function registerCallCommand(program2) {
5362
5594
  );
5363
5595
  }
5364
5596
  let input2 = {};
5365
- const rawInput = opts.inputFile ? readFileSync2(opts.inputFile, "utf8") : opts.input;
5597
+ const rawInput = opts.inputFile ? readFileSync3(opts.inputFile, "utf8") : opts.input;
5366
5598
  if (rawInput && rawInput.trim()) {
5367
5599
  try {
5368
5600
  input2 = JSON.parse(rawInput);
@@ -5493,18 +5725,51 @@ import { spawn as spawn2 } from "child_process";
5493
5725
  import { execFile } from "child_process";
5494
5726
  import {
5495
5727
  createReadStream,
5496
- existsSync as existsSync3,
5728
+ existsSync as existsSync4,
5497
5729
  mkdtempSync,
5498
5730
  readdirSync,
5499
- readFileSync as readFileSync3,
5731
+ readFileSync as readFileSync4,
5500
5732
  rmSync,
5501
5733
  statSync
5502
5734
  } from "fs";
5503
5735
  import { tmpdir } from "os";
5504
- import { basename, dirname, join as join3 } from "path";
5736
+ import { basename, dirname, join as join4 } from "path";
5505
5737
  import { promisify } from "util";
5506
5738
  import open4 from "open";
5507
5739
 
5740
+ // src/lib/agent-setup.ts
5741
+ var SETUP_HINT = "Run `tarout agent init` to allowlist Bash(tarout:*) so tarout commands run without per-command approval prompts.";
5742
+ function isAgentDriven() {
5743
+ return isJsonMode() || isNonInteractiveMode();
5744
+ }
5745
+ function ensureAgentSetup(cwd, disabled = false) {
5746
+ if (disabled || !isAgentDriven() || hasTaroutAllowlist(cwd)) return;
5747
+ const result = scaffoldAgentConfig({ cwd, agent: "claude" });
5748
+ if (isJsonMode()) {
5749
+ outputJsonLine({
5750
+ type: "event",
5751
+ event: "agent_setup_done",
5752
+ files: result.files
5753
+ });
5754
+ } else {
5755
+ for (const file of result.files) {
5756
+ log(colors.dim(` agent setup: ${file.action} ${file.path}`));
5757
+ }
5758
+ }
5759
+ }
5760
+ function emitAgentSetupHint(cwd) {
5761
+ if (!isAgentDriven() || hasTaroutAllowlist(cwd)) return;
5762
+ if (isJsonMode()) {
5763
+ outputJsonLine({
5764
+ type: "event",
5765
+ event: "agent_setup_required",
5766
+ hint: SETUP_HINT
5767
+ });
5768
+ } else {
5769
+ warn(SETUP_HINT);
5770
+ }
5771
+ }
5772
+
5508
5773
  // src/lib/entitlement-remedy.ts
5509
5774
  var planKeyOf = (p) => p.planKey ?? p.key ?? "";
5510
5775
  var addonKeyOf = (a) => a.addonKey ?? a.key ?? "";
@@ -5603,13 +5868,13 @@ function streamDeploymentLogs(deploymentId, options) {
5603
5868
  }
5604
5869
  });
5605
5870
  let buffer = "";
5606
- const done = new Promise((resolve3) => {
5871
+ const done = new Promise((resolve4) => {
5607
5872
  ws.on("close", (code, reason) => {
5608
5873
  if (buffer.length > 0) {
5609
5874
  options.onData(buffer);
5610
5875
  }
5611
5876
  options.onClose?.(code, reason.toString());
5612
- resolve3({ code, reason: reason.toString() });
5877
+ resolve4({ code, reason: reason.toString() });
5613
5878
  });
5614
5879
  });
5615
5880
  ws.on("open", () => {
@@ -5868,7 +6133,7 @@ async function confirmRegion(region) {
5868
6133
  }
5869
6134
  }
5870
6135
  function inspectCurrentProject(cwd = process.cwd()) {
5871
- const packageJson = readJsonFile(join3(cwd, "package.json"));
6136
+ const packageJson = readJsonFile(join4(cwd, "package.json"));
5872
6137
  const dependencies = new Set(
5873
6138
  Object.keys({
5874
6139
  ...packageJson?.dependencies ?? {},
@@ -5916,8 +6181,8 @@ function printProjectInspection(inspection) {
5916
6181
  }
5917
6182
  function readJsonFile(path) {
5918
6183
  try {
5919
- if (!existsSync3(path)) return null;
5920
- return JSON.parse(readFileSync3(path, "utf8"));
6184
+ if (!existsSync4(path)) return null;
6185
+ return JSON.parse(readFileSync4(path, "utf8"));
5921
6186
  } catch {
5922
6187
  return null;
5923
6188
  }
@@ -5926,7 +6191,7 @@ function readTextFile(path, maxBytes) {
5926
6191
  try {
5927
6192
  const stat = statSync(path);
5928
6193
  if (stat.size > maxBytes) return "";
5929
- return readFileSync3(path, "utf8");
6194
+ return readFileSync4(path, "utf8");
5930
6195
  } catch {
5931
6196
  return "";
5932
6197
  }
@@ -5981,7 +6246,7 @@ function collectInspectableFiles(root) {
5981
6246
  }
5982
6247
  for (const entry of entries) {
5983
6248
  if (files.length >= 400) return;
5984
- const fullPath = join3(dir, entry.name);
6249
+ const fullPath = join4(dir, entry.name);
5985
6250
  if (entry.isDirectory()) {
5986
6251
  if (!excludedDirs.has(entry.name)) walk(fullPath, depth + 1);
5987
6252
  continue;
@@ -6033,7 +6298,7 @@ function detectDatabase(dependencies, files, cwd) {
6033
6298
  }
6034
6299
  }
6035
6300
  const prismaSchema = readTextFile(
6036
- join3(cwd, "prisma", "schema.prisma"),
6301
+ join4(cwd, "prisma", "schema.prisma"),
6037
6302
  256 * 1024
6038
6303
  );
6039
6304
  if (/provider\s*=\s*"postgresql"/i.test(prismaSchema)) {
@@ -6114,15 +6379,15 @@ function detectStorage(dependencies, files) {
6114
6379
  return { detected: reasons.length > 0, reasons: uniqueReasons(reasons) };
6115
6380
  }
6116
6381
  function detectGitSource(cwd) {
6117
- const gitPath = join3(cwd, ".git");
6118
- if (!existsSync3(gitPath)) return { hasGit: false };
6119
- let configPath = join3(gitPath, "config");
6382
+ const gitPath = join4(cwd, ".git");
6383
+ if (!existsSync4(gitPath)) return { hasGit: false };
6384
+ let configPath = join4(gitPath, "config");
6120
6385
  try {
6121
6386
  const stat = statSync(gitPath);
6122
6387
  if (stat.isFile()) {
6123
6388
  const content = readTextFile(gitPath, 4096);
6124
6389
  const match = content.match(/gitdir:\s*(.+)/i);
6125
- if (match?.[1]) configPath = join3(cwd, match[1].trim(), "config");
6390
+ if (match?.[1]) configPath = join4(cwd, match[1].trim(), "config");
6126
6391
  }
6127
6392
  } catch {
6128
6393
  }
@@ -6168,7 +6433,12 @@ async function resolveDeploymentTarget(client, profile, appIdentifier, options =
6168
6433
  }
6169
6434
  const linkedProject = getProjectConfig();
6170
6435
  const linkedApp = linkedProject ? findApp3(apps, linkedProject.applicationId) ?? findApp3(apps, linkedProject.name) : void 0;
6171
- if (linkedProject && !linkedApp && !isJsonMode()) {
6436
+ if (linkedApp) {
6437
+ return reuseAppTarget(client, profile, linkedApp, {
6438
+ promptForSource: false
6439
+ });
6440
+ }
6441
+ if (linkedProject && !isJsonMode()) {
6172
6442
  log("");
6173
6443
  log(
6174
6444
  colors.warn(
@@ -6181,12 +6451,10 @@ async function resolveDeploymentTarget(client, profile, appIdentifier, options =
6181
6451
  return createNewAppTarget(client, profile, options);
6182
6452
  }
6183
6453
  if (shouldSkipConfirmation()) {
6184
- if (linkedApp) return reuseAppTarget(client, profile, linkedApp);
6185
6454
  assertConfiguredSourceAllowsCreate(sourcePreference, true);
6186
6455
  return createNewAppTarget(client, profile, options);
6187
6456
  }
6188
6457
  const createValue = "__create__";
6189
- const orderedApps = linkedApp ? [linkedApp, ...apps.filter((a) => a.applicationId !== linkedApp.applicationId)] : apps;
6190
6458
  const selected = await select(
6191
6459
  "Create a new app or reuse an existing one?",
6192
6460
  [
@@ -6194,17 +6462,16 @@ async function resolveDeploymentTarget(client, profile, appIdentifier, options =
6194
6462
  name: `Create a new app from ${colors.cyan(basename(process.cwd()) || "this directory")}`,
6195
6463
  value: createValue
6196
6464
  },
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") : ""}`,
6465
+ ...apps.map((app2) => ({
6466
+ name: `Reuse ${app2.name} ${colors.dim(`(${app2.applicationId.slice(0, 8)})`)}`,
6199
6467
  value: app2.applicationId
6200
6468
  }))
6201
6469
  ],
6202
6470
  {
6203
6471
  field: "deploy_app",
6204
- flag: "--new-app to create a new app, or --app <id|name> to reuse an existing one",
6472
+ 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
6473
  context: {
6206
- linkedApp: linkedApp ? { id: linkedApp.applicationId, name: linkedApp.name } : null,
6207
- apps: orderedApps.map((a) => ({
6474
+ apps: apps.map((a) => ({
6208
6475
  id: a.applicationId,
6209
6476
  name: a.name
6210
6477
  }))
@@ -6236,7 +6503,7 @@ async function createNewAppTarget(client, profile, options) {
6236
6503
  shouldUploadSource: true
6237
6504
  };
6238
6505
  }
6239
- async function reuseAppTarget(client, profile, app) {
6506
+ async function reuseAppTarget(client, profile, app, opts = {}) {
6240
6507
  setProjectConfig({
6241
6508
  applicationId: app.applicationId,
6242
6509
  name: app.name,
@@ -6248,7 +6515,11 @@ async function reuseAppTarget(client, profile, app) {
6248
6515
  app,
6249
6516
  createdApp: false,
6250
6517
  hasConfiguredSource: hasConfiguredSource(details),
6251
- shouldUploadSource: await promptForLocalSourceIfNeeded(details)
6518
+ // A deterministic redeploy (linked app) must not prompt for a source
6519
+ // override — it silently reuses the app's existing source, or the current
6520
+ // folder when `--source upload` is set downstream. Only an interactive
6521
+ // menu reuse offers the override.
6522
+ shouldUploadSource: opts.promptForSource === false ? shouldUseLocalSource(details, { linkedProject: true }) : await promptForLocalSourceIfNeeded(details)
6252
6523
  };
6253
6524
  }
6254
6525
  async function createAppFromCurrentDirectory(client, profile, options = {}) {
@@ -6400,9 +6671,7 @@ async function runInlineUpgrade(client, planKey) {
6400
6671
  planKey,
6401
6672
  wait: true,
6402
6673
  timeoutMs: 6e5,
6403
- openBrowser: isJsonMode() ? void 0 : async (url) => {
6404
- await open4(url);
6405
- },
6674
+ openBrowser: paymentBrowserOpener(),
6406
6675
  onCheckoutOpened: ({ orderId, paymentUrl }) => {
6407
6676
  log("");
6408
6677
  log("Open this URL to complete payment:");
@@ -6452,9 +6721,7 @@ async function runInlineTargetedRemedy(client, remedy) {
6452
6721
  ...input2,
6453
6722
  wait: true,
6454
6723
  timeoutMs: 6e5,
6455
- openBrowser: isJsonMode() ? void 0 : async (url) => {
6456
- await open4(url);
6457
- },
6724
+ openBrowser: paymentBrowserOpener(),
6458
6725
  onCheckoutOpened: ({ orderId, paymentUrl }) => {
6459
6726
  log("");
6460
6727
  log("Open this URL to complete payment:");
@@ -6651,7 +6918,8 @@ async function emitNeedsUpgrade(client, err, requestedPlan, retryCommand) {
6651
6918
  suggestedTarget: remedy.targetKey,
6652
6919
  nextCommand: remedy.command,
6653
6920
  options,
6654
- hint
6921
+ hint,
6922
+ permissionHint: AGENT_BILLING_PERMISSION_HINT
6655
6923
  });
6656
6924
  }
6657
6925
  async function listFreeDatabasesSafely(client) {
@@ -7725,8 +7993,8 @@ async function uploadCurrentDirectorySource(client, applicationId, appName) {
7725
7993
  }
7726
7994
  }
7727
7995
  async function createSourceArchive() {
7728
- const tempDir = mkdtempSync(join3(tmpdir(), "tarout-source-"));
7729
- const archivePath = join3(tempDir, "source.zip");
7996
+ const tempDir = mkdtempSync(join4(tmpdir(), "tarout-source-"));
7997
+ const archivePath = join4(tempDir, "source.zip");
7730
7998
  const excludes = [
7731
7999
  ".git/*",
7732
8000
  ".tarout/*",
@@ -7800,8 +8068,12 @@ function registerDeployCommands(program2) {
7800
8068
  ).option("--install-command <cmd>", "Custom install command").option("--build-command <cmd>", "Custom build command").option(
7801
8069
  "--output-directory <path>",
7802
8070
  "Build output directory (static assets)"
7803
- ).option("--start-command <cmd>", "Custom start command").option("-w, --wait", "Wait for deployment to complete and stream logs").option("--watch", "Alias for --wait").action(async (appIdentifier, options) => {
8071
+ ).option("--start-command <cmd>", "Custom start command").option("-w, --wait", "Wait for deployment to complete and stream logs").option("--watch", "Alias for --wait").option(
8072
+ "--no-agent-setup",
8073
+ "Don't auto-write the agent permission allowlist (CLAUDE.md / .claude/settings.local.json) on first run"
8074
+ ).action(async (appIdentifier, options) => {
7804
8075
  try {
8076
+ ensureAgentSetup(process.cwd(), options.agentSetup === false);
7805
8077
  const inspection = inspectCurrentProject();
7806
8078
  printProjectInspection(inspection);
7807
8079
  const profile = await ensureAuthenticatedForDeploy(options);
@@ -8231,7 +8503,7 @@ function registerDeployCommands(program2) {
8231
8503
  name: `${colors.cyan(d.deploymentId.slice(0, 8))} - ${d.title || "Deployment"} (${formatDate5(d.createdAt)})${index === 0 ? colors.dim(" [current]") : ""}`,
8232
8504
  value: d.deploymentId
8233
8505
  }));
8234
- const { select: select2 } = await import("./prompts-JH6YBHHV.js");
8506
+ const { select: select2 } = await import("./prompts-D72VJYWE.js");
8235
8507
  targetDeploymentId = await select2(
8236
8508
  "Select deployment:",
8237
8509
  choices,
@@ -8250,7 +8522,7 @@ function registerDeployCommands(program2) {
8250
8522
  ` Created: ${targetDeployment ? formatDate5(targetDeployment.createdAt) : colors.dim("unknown")}`
8251
8523
  );
8252
8524
  log("");
8253
- const { confirm: confirm2 } = await import("./prompts-JH6YBHHV.js");
8525
+ const { confirm: confirm2 } = await import("./prompts-D72VJYWE.js");
8254
8526
  const confirmed = await confirm2(
8255
8527
  "Are you sure you want to rollback?",
8256
8528
  false,
@@ -8382,7 +8654,7 @@ function formatDate5(date) {
8382
8654
  });
8383
8655
  }
8384
8656
  function sleep(ms) {
8385
- return new Promise((resolve3) => setTimeout(resolve3, ms));
8657
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
8386
8658
  }
8387
8659
  async function streamDeploymentWithLogs(client, deploymentId, appName, applicationId) {
8388
8660
  stopSpinner();
@@ -11953,7 +12225,7 @@ function truncate(str, max) {
11953
12225
  }
11954
12226
 
11955
12227
  // src/commands/env.ts
11956
- import { chmodSync, existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
12228
+ import { chmodSync, existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
11957
12229
  function registerEnvCommands(program2) {
11958
12230
  const env = program2.command("env").argument("<app>", "Application ID or name").description("Manage environment variables");
11959
12231
  env.command("list").alias("ls").description("List all environment variables").option("--reveal", "Show actual values (not masked)").action(async (options, command) => {
@@ -12113,7 +12385,7 @@ function registerEnvCommands(program2) {
12113
12385
  );
12114
12386
  throw new NotFoundError("Application", appIdentifier, suggestions);
12115
12387
  }
12116
- if (existsSync4(options.output) && !shouldSkipConfirmation()) {
12388
+ if (existsSync5(options.output) && !shouldSkipConfirmation()) {
12117
12389
  succeedSpinner();
12118
12390
  const confirmed = await confirm(
12119
12391
  `File ${options.output} already exists. Overwrite?`,
@@ -12134,7 +12406,7 @@ function registerEnvCommands(program2) {
12134
12406
  format: "dotenv",
12135
12407
  maskSecrets: !options.reveal
12136
12408
  });
12137
- writeFileSync2(options.output, result.content, { mode: 384 });
12409
+ writeFileSync3(options.output, result.content, { mode: 384 });
12138
12410
  try {
12139
12411
  chmodSync(options.output, 384);
12140
12412
  } catch {
@@ -12153,10 +12425,10 @@ function registerEnvCommands(program2) {
12153
12425
  try {
12154
12426
  if (!isLoggedIn()) throw new AuthError();
12155
12427
  const appIdentifier = command.parent.parent.args[0];
12156
- if (!existsSync4(options.input)) {
12428
+ if (!existsSync5(options.input)) {
12157
12429
  throw new InvalidArgumentError(`File not found: ${options.input}`);
12158
12430
  }
12159
- const content = readFileSync4(options.input, "utf-8");
12431
+ const content = readFileSync5(options.input, "utf-8");
12160
12432
  const client = getApiClient();
12161
12433
  const _spinner = startSpinner("Uploading environment variables...");
12162
12434
  const apps = await client.application.allByOrganization.query();
@@ -12380,7 +12652,7 @@ ${colors.bold(key)}: ${maskValue(val.value || String(v))}
12380
12652
  } else {
12381
12653
  const _raw = await import("process");
12382
12654
  log('Enter JSON key-value object (e.g. {"KEY":"value"}):');
12383
- const input2 = await (await import("./prompts-JH6YBHHV.js")).input(
12655
+ const input2 = await (await import("./prompts-D72VJYWE.js")).input(
12384
12656
  "JSON:"
12385
12657
  );
12386
12658
  vars = JSON.parse(input2);
@@ -12403,7 +12675,7 @@ ${colors.bold(key)}: ${maskValue(val.value || String(v))}
12403
12675
  try {
12404
12676
  if (!isLoggedIn()) throw new AuthError();
12405
12677
  if (!shouldSkipConfirmation()) {
12406
- const { confirm: confirmFn } = await import("./prompts-JH6YBHHV.js");
12678
+ const { confirm: confirmFn } = await import("./prompts-D72VJYWE.js");
12407
12679
  const ok = await confirmFn(
12408
12680
  `Delete ${keys.length} variable(s)?`,
12409
12681
  false
@@ -12442,7 +12714,7 @@ ${colors.bold(key)}: ${maskValue(val.value || String(v))}
12442
12714
  );
12443
12715
  if (!app) throw new NotFoundError("Application", appIdentifier);
12444
12716
  if (!shouldSkipConfirmation()) {
12445
- const { confirm: confirmFn } = await import("./prompts-JH6YBHHV.js");
12717
+ const { confirm: confirmFn } = await import("./prompts-D72VJYWE.js");
12446
12718
  const ok = await confirmFn(
12447
12719
  `Copy env vars from ${fromEnvId} to ${toEnvId}?`,
12448
12720
  false
@@ -12890,8 +13162,8 @@ function registerInboxCommands(program2) {
12890
13162
  }
12891
13163
 
12892
13164
  // 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";
13165
+ import { existsSync as existsSync6, writeFileSync as writeFileSync4 } from "fs";
13166
+ import { basename as basename2, join as join5, resolve as resolve2 } from "path";
12895
13167
  var DEFAULT_REGION2 = "me-central2";
12896
13168
  var STARTER_INDEX_JS = `import { createServer } from "node:http";
12897
13169
 
@@ -12909,8 +13181,8 @@ function toPackageName(name) {
12909
13181
  }
12910
13182
  function scaffoldStarter(cwd) {
12911
13183
  const written = [];
12912
- const pkgPath = join4(cwd, "package.json");
12913
- if (existsSync5(pkgPath)) return written;
13184
+ const pkgPath = join5(cwd, "package.json");
13185
+ if (existsSync6(pkgPath)) return written;
12914
13186
  const pkg = {
12915
13187
  name: toPackageName(basename2(cwd) || "tarout-app"),
12916
13188
  version: "0.1.0",
@@ -12918,12 +13190,12 @@ function scaffoldStarter(cwd) {
12918
13190
  type: "module",
12919
13191
  scripts: { start: "node index.js", dev: "node index.js" }
12920
13192
  };
12921
- writeFileSync3(pkgPath, `${JSON.stringify(pkg, null, 2)}
13193
+ writeFileSync4(pkgPath, `${JSON.stringify(pkg, null, 2)}
12922
13194
  `);
12923
13195
  written.push("package.json");
12924
- const indexPath = join4(cwd, "index.js");
12925
- if (!existsSync5(indexPath)) {
12926
- writeFileSync3(indexPath, STARTER_INDEX_JS);
13196
+ const indexPath = join5(cwd, "index.js");
13197
+ if (!existsSync6(indexPath)) {
13198
+ writeFileSync4(indexPath, STARTER_INDEX_JS);
12927
13199
  written.push("index.js");
12928
13200
  }
12929
13201
  return written;
@@ -12935,9 +13207,9 @@ function writeEnvFile(cwd, vars) {
12935
13207
  const lines = Object.entries(vars).map(
12936
13208
  ([k, v]) => `${k}=${envValueNeedsQuote(v) ? JSON.stringify(v) : v}`
12937
13209
  );
12938
- const primary = join4(cwd, ".env");
12939
- const target = existsSync5(primary) ? join4(cwd, ".env.tarout") : primary;
12940
- writeFileSync3(target, `${lines.join("\n")}
13210
+ const primary = join5(cwd, ".env");
13211
+ const target = existsSync6(primary) ? join5(cwd, ".env.tarout") : primary;
13212
+ writeFileSync4(target, `${lines.join("\n")}
12941
13213
  `, { mode: 384 });
12942
13214
  return target;
12943
13215
  }
@@ -12954,10 +13226,14 @@ function registerInitCommand(program2) {
12954
13226
  ).option("-r, --region <region>", "Deployment region", DEFAULT_REGION2).option("--description <text>", "Description for the newly created app").option(
12955
13227
  "--database <type>",
12956
13228
  "Provision a database: none, postgres, or mysql (defaults to auto-detected)"
12957
- ).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) => {
13229
+ ).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").option(
13230
+ "--no-agent-setup",
13231
+ "Don't auto-write the agent permission allowlist (CLAUDE.md / .claude/settings.local.json) on first run"
13232
+ ).action(async (cwdArg, options) => {
12958
13233
  try {
12959
- const cwd = cwdArg ? resolve(cwdArg) : process.cwd();
13234
+ const cwd = cwdArg ? resolve2(cwdArg) : process.cwd();
12960
13235
  if (cwdArg) process.chdir(cwd);
13236
+ ensureAgentSetup(cwd, options.agentSetup === false);
12961
13237
  if (options.scaffold) {
12962
13238
  const files = scaffoldStarter(cwd);
12963
13239
  emitEvent({ event: "scaffold_done", files });
@@ -13019,7 +13295,8 @@ function registerInitCommand(program2) {
13019
13295
  outputError("NEEDS_UPGRADE", message, {
13020
13296
  suggestedPlan: inferSuggestedPlan(options.plan),
13021
13297
  failedEntitlementKey: extractEntitlementKeyFromError(err),
13022
- hint: "Run `tarout billing upgrade <plan> --wait` to add slots, then retry `tarout init`."
13298
+ hint: "Run `tarout billing upgrade <plan> --wait` to add slots, then retry `tarout init`.",
13299
+ permissionHint: AGENT_BILLING_PERMISSION_HINT
13023
13300
  });
13024
13301
  exit(ExitCode.PERMISSION_DENIED);
13025
13302
  }
@@ -13156,8 +13433,8 @@ function registerKeysCommands(program2) {
13156
13433
  });
13157
13434
  }
13158
13435
  if (!publicKey && options.file) {
13159
- const { readFileSync: readFileSync5 } = await import("fs");
13160
- publicKey = readFileSync5(options.file, "utf-8").trim();
13436
+ const { readFileSync: readFileSync6 } = await import("fs");
13437
+ publicKey = readFileSync6(options.file, "utf-8").trim();
13161
13438
  }
13162
13439
  if (!publicKey) {
13163
13440
  log(
@@ -19345,7 +19622,7 @@ function truncate3(str, max) {
19345
19622
  }
19346
19623
 
19347
19624
  // src/commands/up.ts
19348
- import { resolve as resolve2 } from "path";
19625
+ import { resolve as resolve3 } from "path";
19349
19626
  var DEFAULT_REGION3 = "me-central2";
19350
19627
  function normalizeSource(value) {
19351
19628
  if (!value) return "upload";
@@ -19402,10 +19679,14 @@ function registerUpCommand(program2) {
19402
19679
  ).option("--start-command <cmd>", "Custom start command").option(
19403
19680
  "--idempotency-key <key>",
19404
19681
  "Idempotency key for safe retries (Phase 2; logged only in v1)"
19682
+ ).option(
19683
+ "--no-agent-setup",
19684
+ "Don't auto-write the agent permission allowlist (CLAUDE.md / .claude/settings.local.json) on first run"
19405
19685
  ).action(async (cwdArg, options) => {
19406
19686
  try {
19407
- const cwd = cwdArg ? resolve2(cwdArg) : process.cwd();
19687
+ const cwd = cwdArg ? resolve3(cwdArg) : process.cwd();
19408
19688
  if (cwdArg) process.chdir(cwd);
19689
+ ensureAgentSetup(cwd, options.agentSetup === false);
19409
19690
  const source = normalizeSource(options.source);
19410
19691
  const idempotencyKey = options.idempotencyKey?.trim();
19411
19692
  if (idempotencyKey) {
@@ -19905,7 +20186,7 @@ var program = new Command();
19905
20186
  program.name("tarout").description("Tarout PaaS Command Line Interface").version(package_default.version).option("--json", "Output as JSON (machine-readable)").option("-y, --yes", "Skip all confirmation prompts").option(
19906
20187
  "--non-interactive",
19907
20188
  "Fail fast on missing input (emit needs_input + exit 6 instead of prompting on TTY)"
19908
- ).option("-q, --quiet", "Minimal output").option("-v, --verbose", "Extra debug information").option("--no-color", "Disable colored output").hook("preAction", (thisCommand) => {
20189
+ ).option("-q, --quiet", "Minimal output").option("-v, --verbose", "Extra debug information").option("--no-color", "Disable colored output").hook("preAction", (thisCommand, actionCommand) => {
19909
20190
  const opts = thisCommand.opts();
19910
20191
  const stdinIsTTY = Boolean(process.stdin.isTTY);
19911
20192
  setGlobalOptions({
@@ -19916,11 +20197,18 @@ program.name("tarout").description("Tarout PaaS Command Line Interface").version
19916
20197
  verbose: opts.verbose || false,
19917
20198
  noColor: opts.color === false
19918
20199
  });
20200
+ const sub = actionCommand?.name();
20201
+ const isAgentNamespace = actionCommand?.parent?.name() === "agent";
20202
+ const autoRunsSetup = !!sub && ["up", "deploy", "init"].includes(sub);
20203
+ if (!isAgentNamespace && !autoRunsSetup) {
20204
+ emitAgentSetupHint(process.cwd());
20205
+ }
19919
20206
  });
19920
20207
  registerAuthCommands(program);
19921
20208
  registerAppsCommands(program);
19922
20209
  registerDeployCommands(program);
19923
20210
  registerInitCommand(program);
20211
+ registerAgentCommands(program);
19924
20212
  registerUpCommand(program);
19925
20213
  registerLogsCommand(program);
19926
20214
  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.10.0",
4
4
  "description": "Tarout CLI — the Saudi cloud platform for coding agents",
5
5
  "type": "module",
6
6
  "bin": {