@dura-run/cli 0.1.5 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/dura.js CHANGED
@@ -2425,7 +2425,7 @@ async function openBrowser(url) {
2425
2425
  child.unref();
2426
2426
  }
2427
2427
  function registerLoginCommand(program2) {
2428
- program2.command("login").description("Authenticate with the Dura control plane").option("--api-key <key>", "Authenticate using an API key").option("--api-url <url>", "Set the API base URL").action(async (opts, cmd) => {
2428
+ program2.command("login").description("Authenticate with the dura control plane").option("--api-key <key>", "Authenticate using an API key").option("--api-url <url>", "Set the API base URL").action(async (opts, cmd) => {
2429
2429
  const output = getOutput(cmd);
2430
2430
  if (opts.apiKey) {
2431
2431
  const updates = { apiKey: opts.apiKey };
@@ -2677,7 +2677,7 @@ __export(exports_logout, {
2677
2677
  registerLogoutCommand: () => registerLogoutCommand
2678
2678
  });
2679
2679
  function registerLogoutCommand(program2) {
2680
- program2.command("logout").description("Log out from the Dura control plane").option("--revoke", "Also revoke the API key on the server").option("--api-url <url>", "API base URL override").action(async (opts, cmd) => {
2680
+ program2.command("logout").description("Log out from the dura control plane").option("--revoke", "Also revoke the API key on the server").option("--api-url <url>", "API base URL override").action(async (opts, cmd) => {
2681
2681
  const output = getOutput(cmd);
2682
2682
  const token = getAuthToken();
2683
2683
  if (!token) {
@@ -2708,20 +2708,20 @@ var init_logout = __esm(() => {
2708
2708
  });
2709
2709
 
2710
2710
  // src/templates/dura.json.ts
2711
- function duraJsonTemplate(name) {
2711
+ function duraJsonTemplate(name, kind = "get") {
2712
+ const trigger = kind === "cron" ? { type: "cron", schedule: "0 * * * *" } : {
2713
+ type: "http",
2714
+ method: kind.toUpperCase(),
2715
+ path: "/hello",
2716
+ public: false
2717
+ };
2712
2718
  const manifest = {
2713
2719
  name,
2714
2720
  automations: [
2715
2721
  {
2716
2722
  name: "hello",
2717
2723
  entrypoint: "routes/hello.ts",
2718
- triggers: [
2719
- {
2720
- type: "http",
2721
- method: "GET",
2722
- path: "/hello"
2723
- }
2724
- ]
2724
+ triggers: [trigger]
2725
2725
  }
2726
2726
  ]
2727
2727
  };
@@ -2730,7 +2730,18 @@ function duraJsonTemplate(name) {
2730
2730
  }
2731
2731
 
2732
2732
  // src/templates/hello.ts
2733
- var HELLO_TEMPLATE = `import type { DuraRequest, DuraResponse } from "@dura/sdk";
2733
+ function helloTemplate(kind) {
2734
+ switch (kind) {
2735
+ case "post":
2736
+ return POST_TEMPLATE;
2737
+ case "cron":
2738
+ return CRON_TEMPLATE;
2739
+ case "get":
2740
+ default:
2741
+ return GET_TEMPLATE;
2742
+ }
2743
+ }
2744
+ var GET_TEMPLATE = `import type { DuraRequest, DuraResponse } from "@dura/sdk";
2734
2745
 
2735
2746
  export default async function handler(req: DuraRequest): Promise<DuraResponse> {
2736
2747
  return {
@@ -2739,7 +2750,30 @@ export default async function handler(req: DuraRequest): Promise<DuraResponse> {
2739
2750
  body: { message: "Hello from Dura!" },
2740
2751
  };
2741
2752
  }
2742
- `;
2753
+ `, POST_TEMPLATE = `import type { DuraRequest, DuraResponse } from "@dura/sdk";
2754
+
2755
+ export default async function handler(req: DuraRequest): Promise<DuraResponse> {
2756
+ const { name = "world" } = (req.body ?? {}) as { name?: string };
2757
+ return {
2758
+ status: 200,
2759
+ headers: { "content-type": "application/json" },
2760
+ body: { greeting: \`hello, \${name}\` },
2761
+ };
2762
+ }
2763
+ `, CRON_TEMPLATE = `import type { DuraRequest, DuraResponse } from "@dura/sdk";
2764
+
2765
+ export default async function handler(_req: DuraRequest): Promise<DuraResponse> {
2766
+ // Runs on the cron schedule defined in dura.json. Add your job here.
2767
+ return {
2768
+ status: 200,
2769
+ headers: { "content-type": "application/json" },
2770
+ body: { ranAt: new Date().toISOString() },
2771
+ };
2772
+ }
2773
+ `, HELLO_TEMPLATE;
2774
+ var init_hello = __esm(() => {
2775
+ HELLO_TEMPLATE = GET_TEMPLATE;
2776
+ });
2743
2777
 
2744
2778
  // src/commands/new.ts
2745
2779
  var exports_new = {};
@@ -2749,12 +2783,17 @@ __export(exports_new, {
2749
2783
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
2750
2784
  import { join as join2, resolve } from "node:path";
2751
2785
  function registerNewCommand(program2) {
2752
- program2.command("new <name>").description("Scaffold a new Dura project").option("--dir <path>", "Parent directory (defaults to current dir)").option("--org <id>", "Organization ID for API registration").option("--template <slug>", "Fork a template as the starting point").action(async (name, opts, cmd) => {
2786
+ program2.command("new <name>").description("Scaffold a new dura project").option("--dir <path>", "Parent directory (defaults to current dir)").option("--org <id>", "Organization ID for API registration").option("--template <slug>", "Fork a template as the starting point").option("--trigger <kind>", "Default trigger kind for the scaffold: get | post | cron", "get").action(async (name, opts, cmd) => {
2753
2787
  const output = getOutput(cmd);
2754
2788
  if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name)) {
2755
2789
  output.error("INVALID_NAME", "Project name must start with a letter and contain only letters, numbers, hyphens, and underscores");
2756
2790
  return;
2757
2791
  }
2792
+ const triggerKind = (opts.trigger ?? "get").toLowerCase();
2793
+ if (!["get", "post", "cron"].includes(triggerKind)) {
2794
+ output.error("INVALID_TRIGGER", `Unknown trigger kind: ${opts.trigger}`, "Use one of: get, post, cron");
2795
+ return;
2796
+ }
2758
2797
  const parentDir = opts.dir ? resolve(opts.dir) : process.cwd();
2759
2798
  const projectDir = join2(parentDir, name);
2760
2799
  if (existsSync2(projectDir)) {
@@ -2802,8 +2841,8 @@ Set required secrets: ${result.requiredSecrets.join(", ")}`);
2802
2841
  }
2803
2842
  mkdirSync2(join2(projectDir, "routes"), { recursive: true });
2804
2843
  mkdirSync2(join2(projectDir, "jobs"), { recursive: true });
2805
- writeFileSync2(join2(projectDir, "dura.json"), duraJsonTemplate(name));
2806
- writeFileSync2(join2(projectDir, "routes", "hello.ts"), HELLO_TEMPLATE);
2844
+ writeFileSync2(join2(projectDir, "dura.json"), duraJsonTemplate(name, triggerKind));
2845
+ writeFileSync2(join2(projectDir, "routes", "hello.ts"), helloTemplate(triggerKind));
2807
2846
  writeFileSync2(join2(projectDir, ".gitignore"), GITIGNORE_CONTENT);
2808
2847
  const token = getAuthToken();
2809
2848
  if (token) {
@@ -2830,7 +2869,7 @@ Set required secrets: ${result.requiredSecrets.join(", ")}`);
2830
2869
  output.info("No org specified. Run dura login and set a default org to register projects.");
2831
2870
  }
2832
2871
  } else {
2833
- output.info("Run dura login to register your project with the Dura control plane.");
2872
+ output.info("Run dura login to register your project with the dura control plane.");
2834
2873
  }
2835
2874
  output.success({
2836
2875
  created: true,
@@ -2847,36 +2886,61 @@ dist
2847
2886
  `;
2848
2887
  var init_new = __esm(() => {
2849
2888
  init_src3();
2889
+ init_hello();
2850
2890
  init_api_client();
2851
2891
  init_config_store();
2852
2892
  });
2853
2893
 
2894
+ // src/lib/project-id.ts
2895
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
2896
+ import { join as join3 } from "node:path";
2897
+ function resolveProjectId(opts = {}) {
2898
+ if (opts.project && opts.project.length > 0)
2899
+ return opts.project;
2900
+ const dir = opts.dir ?? process.cwd();
2901
+ const manifestPath = join3(dir, "dura.json");
2902
+ if (!existsSync3(manifestPath))
2903
+ return null;
2904
+ try {
2905
+ const raw = JSON.parse(readFileSync3(manifestPath, "utf-8"));
2906
+ return raw.projectId ?? null;
2907
+ } catch {
2908
+ return null;
2909
+ }
2910
+ }
2911
+ var init_project_id = () => {};
2912
+
2854
2913
  // src/commands/secrets.ts
2855
2914
  var exports_secrets = {};
2856
2915
  __export(exports_secrets, {
2857
2916
  registerSecretsCommand: () => registerSecretsCommand
2858
2917
  });
2859
2918
  import { writeFileSync as writeFileSync3 } from "node:fs";
2860
- import { join as join3 } from "node:path";
2919
+ import { join as join4 } from "node:path";
2861
2920
  function resolveAuth(opts, output) {
2921
+ const projectId = resolveProjectId({ project: opts.project });
2922
+ if (!projectId) {
2923
+ output.error("PROJECT_REQUIRED", "No project ID. Use --project or run this command inside a dura.json project.");
2924
+ return null;
2925
+ }
2862
2926
  const token = opts.token || getAuthToken();
2863
2927
  if (!token) {
2864
2928
  output.error("AUTH_REQUIRED", "Not logged in", "Run: dura login");
2865
2929
  return null;
2866
2930
  }
2867
2931
  const apiUrl = opts.apiUrl || getApiUrl();
2868
- return { client: new ApiClient(apiUrl, token) };
2932
+ return { client: new ApiClient(apiUrl, token), projectId };
2869
2933
  }
2870
2934
  function registerSecretsCommand(program2) {
2871
2935
  const secrets = program2.command("secrets").description("Manage project secrets");
2872
- secrets.command("set <name> <value>").description("Set a secret value").requiredOption("--project <id>", "Project ID").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (name, value, opts, cmd) => {
2936
+ secrets.command("set <name> <value>").description("Set a secret value").option("--project <id>", "Project ID (defaults to dura.json)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (name, value, opts, cmd) => {
2873
2937
  const output = getOutput(cmd);
2874
2938
  const auth = resolveAuth(opts, output);
2875
2939
  if (!auth)
2876
2940
  return;
2877
- const { client } = auth;
2941
+ const { client, projectId } = auth;
2878
2942
  try {
2879
- const result = await client.put(`/api/v1/projects/${opts.project}/secrets`, { name, value });
2943
+ const result = await client.put(`/api/v1/projects/${projectId}/secrets`, { name, value });
2880
2944
  output.success(result);
2881
2945
  } catch (err) {
2882
2946
  if (err instanceof Error) {
@@ -2884,14 +2948,14 @@ function registerSecretsCommand(program2) {
2884
2948
  }
2885
2949
  }
2886
2950
  });
2887
- secrets.command("list").description("List secret names (values never shown)").requiredOption("--project <id>", "Project ID").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
2951
+ secrets.command("list").description("List secret names (values never shown)").option("--project <id>", "Project ID (defaults to dura.json)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
2888
2952
  const output = getOutput(cmd);
2889
2953
  const auth = resolveAuth(opts, output);
2890
2954
  if (!auth)
2891
2955
  return;
2892
- const { client } = auth;
2956
+ const { client, projectId } = auth;
2893
2957
  try {
2894
- const result = await client.get(`/api/v1/projects/${opts.project}/secrets`);
2958
+ const result = await client.get(`/api/v1/projects/${projectId}/secrets`);
2895
2959
  output.table(["Name", "Created", "Updated"], result.map((s) => [s.name, s.createdAt, s.updatedAt]));
2896
2960
  } catch (err) {
2897
2961
  if (err instanceof Error) {
@@ -2899,7 +2963,7 @@ function registerSecretsCommand(program2) {
2899
2963
  }
2900
2964
  }
2901
2965
  });
2902
- secrets.command("remove <name>").description("Remove a secret").requiredOption("--project <id>", "Project ID").option("--confirm", "Confirm this destructive operation").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (name, opts, cmd) => {
2966
+ secrets.command("remove <name>").description("Remove a secret").option("--project <id>", "Project ID (defaults to dura.json)").option("--confirm", "Confirm this destructive operation").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (name, opts, cmd) => {
2903
2967
  const output = getOutput(cmd);
2904
2968
  if (!opts.confirm) {
2905
2969
  output.error("CONFIRM_REQUIRED", "This is a destructive operation. Re-run with --confirm to proceed.");
@@ -2908,9 +2972,9 @@ function registerSecretsCommand(program2) {
2908
2972
  const auth = resolveAuth(opts, output);
2909
2973
  if (!auth)
2910
2974
  return;
2911
- const { client } = auth;
2975
+ const { client, projectId } = auth;
2912
2976
  try {
2913
- await client.delete(`/api/v1/projects/${opts.project}/secrets/${name}`);
2977
+ await client.delete(`/api/v1/projects/${projectId}/secrets/${name}`);
2914
2978
  output.success({ deleted: true, name });
2915
2979
  } catch (err) {
2916
2980
  if (err instanceof Error) {
@@ -2918,17 +2982,16 @@ function registerSecretsCommand(program2) {
2918
2982
  }
2919
2983
  }
2920
2984
  });
2921
- secrets.command("pull").description("Write secrets to .env.local for local development").requiredOption("--project <id>", "Project ID").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").option("--dir <path>", "Directory to write .env.local to", ".").action(async (opts, cmd) => {
2985
+ secrets.command("pull").description("Write secrets to .env.local for local development").option("--project <id>", "Project ID (defaults to dura.json)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").option("--dir <path>", "Directory to write .env.local to", ".").action(async (opts, cmd) => {
2922
2986
  const output = getOutput(cmd);
2923
2987
  const auth = resolveAuth(opts, output);
2924
2988
  if (!auth)
2925
2989
  return;
2926
- const { client } = auth;
2990
+ const { client, projectId } = auth;
2927
2991
  try {
2928
- await client.get(`/api/v1/projects/${opts.project}/secrets`);
2929
- const values = await client.get(`/api/v1/projects/${opts.project}/secrets/pull`);
2992
+ const values = await client.get(`/api/v1/projects/${projectId}/secrets/pull`);
2930
2993
  const lines = Object.entries(values).map(([k, v]) => `${k}="${v.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n")}"`);
2931
- const envPath = join3(opts.dir, ".env.local");
2994
+ const envPath = join4(opts.dir, ".env.local");
2932
2995
  writeFileSync3(envPath, lines.join(`
2933
2996
  `) + `
2934
2997
  `, { mode: 384 });
@@ -2947,6 +3010,7 @@ var init_secrets = __esm(() => {
2947
3010
  init_src3();
2948
3011
  init_api_client();
2949
3012
  init_config_store();
3013
+ init_project_id();
2950
3014
  });
2951
3015
 
2952
3016
  // ../shared/src/constants/defaults.ts
@@ -7010,7 +7074,8 @@ var init_manifest = __esm(() => {
7010
7074
  exports_external.object({
7011
7075
  type: exports_external.literal("http"),
7012
7076
  method: exports_external.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).optional(),
7013
- path: exports_external.string().optional()
7077
+ path: exports_external.string().optional(),
7078
+ public: exports_external.boolean().optional()
7014
7079
  }),
7015
7080
  exports_external.object({
7016
7081
  type: exports_external.literal("cron"),
@@ -10021,7 +10086,7 @@ var init_sql = __esm(() => {
10021
10086
  return new SQL([new StringChunk(str)]);
10022
10087
  }
10023
10088
  sql2.raw = raw;
10024
- function join4(chunks, separator) {
10089
+ function join5(chunks, separator) {
10025
10090
  const result = [];
10026
10091
  for (const [i, chunk] of chunks.entries()) {
10027
10092
  if (i > 0 && separator !== undefined) {
@@ -10031,7 +10096,7 @@ var init_sql = __esm(() => {
10031
10096
  }
10032
10097
  return new SQL(result);
10033
10098
  }
10034
- sql2.join = join4;
10099
+ sql2.join = join5;
10035
10100
  function identifier(value) {
10036
10101
  return new Name(value);
10037
10102
  }
@@ -11921,7 +11986,7 @@ var init_select2 = __esm(() => {
11921
11986
  return (table, on) => {
11922
11987
  const baseTableName = this.tableName;
11923
11988
  const tableName = getTableLikeName(table);
11924
- if (typeof tableName === "string" && this.config.joins?.some((join4) => join4.alias === tableName)) {
11989
+ if (typeof tableName === "string" && this.config.joins?.some((join5) => join5.alias === tableName)) {
11925
11990
  throw new Error(`Alias "${tableName}" is already used in this query`);
11926
11991
  }
11927
11992
  if (!this.isPartialSelect) {
@@ -12161,7 +12226,7 @@ var init_update = __esm(() => {
12161
12226
  createJoin(joinType) {
12162
12227
  return (table, on) => {
12163
12228
  const tableName = getTableLikeName(table);
12164
- if (typeof tableName === "string" && this.config.joins.some((join4) => join4.alias === tableName)) {
12229
+ if (typeof tableName === "string" && this.config.joins.some((join5) => join5.alias === tableName)) {
12165
12230
  throw new Error(`Alias "${tableName}" is already used in this query`);
12166
12231
  }
12167
12232
  if (typeof on === "function") {
@@ -12211,10 +12276,10 @@ var init_update = __esm(() => {
12211
12276
  const fromFields = this.getTableLikeFields(this.config.from);
12212
12277
  fields[tableName] = fromFields;
12213
12278
  }
12214
- for (const join4 of this.config.joins) {
12215
- const tableName2 = getTableLikeName(join4.table);
12216
- if (typeof tableName2 === "string" && !is(join4.table, SQL)) {
12217
- const fromFields = this.getTableLikeFields(join4.table);
12279
+ for (const join5 of this.config.joins) {
12280
+ const tableName2 = getTableLikeName(join5.table);
12281
+ if (typeof tableName2 === "string" && !is(join5.table, SQL)) {
12282
+ const fromFields = this.getTableLikeFields(join5.table);
12218
12283
  fields[tableName2] = fromFields;
12219
12284
  }
12220
12285
  }
@@ -13588,16 +13653,16 @@ var init_src2 = __esm(() => {
13588
13653
  });
13589
13654
 
13590
13655
  // src/lib/manifest.ts
13591
- import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
13592
- import { join as join4 } from "node:path";
13656
+ import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
13657
+ import { join as join5 } from "node:path";
13593
13658
  function readManifest(dir) {
13594
- const path = join4(dir, "dura.json");
13595
- if (!existsSync3(path)) {
13659
+ const path = join5(dir, "dura.json");
13660
+ if (!existsSync4(path)) {
13596
13661
  throw new ManifestError(`No dura.json found in ${dir}`);
13597
13662
  }
13598
13663
  let raw;
13599
13664
  try {
13600
- raw = JSON.parse(readFileSync3(path, "utf-8"));
13665
+ raw = JSON.parse(readFileSync4(path, "utf-8"));
13601
13666
  } catch {
13602
13667
  throw new ManifestError("dura.json is not valid JSON");
13603
13668
  }
@@ -13608,14 +13673,14 @@ function readManifest(dir) {
13608
13673
  return result.manifest;
13609
13674
  }
13610
13675
  function readRawManifest(dir) {
13611
- const path = join4(dir, "dura.json");
13612
- if (!existsSync3(path)) {
13676
+ const path = join5(dir, "dura.json");
13677
+ if (!existsSync4(path)) {
13613
13678
  throw new ManifestError(`No dura.json found in ${dir}`);
13614
13679
  }
13615
- return JSON.parse(readFileSync3(path, "utf-8"));
13680
+ return JSON.parse(readFileSync4(path, "utf-8"));
13616
13681
  }
13617
13682
  function writeRawManifest(dir, data) {
13618
- const path = join4(dir, "dura.json");
13683
+ const path = join5(dir, "dura.json");
13619
13684
  writeFileSync4(path, JSON.stringify(data, null, 2) + `
13620
13685
  `);
13621
13686
  }
@@ -13800,10 +13865,10 @@ function generateDeploymentManifest(_projectDir, manifest) {
13800
13865
  var init_manifest_generator = () => {};
13801
13866
 
13802
13867
  // src/lib/bundler.ts
13803
- import { join as join5, resolve as resolve2 } from "node:path";
13868
+ import { join as join6, resolve as resolve2 } from "node:path";
13804
13869
  import {
13805
- existsSync as existsSync4,
13806
- readFileSync as readFileSync4,
13870
+ existsSync as existsSync5,
13871
+ readFileSync as readFileSync5,
13807
13872
  writeFileSync as writeFileSync5,
13808
13873
  mkdirSync as mkdirSync3,
13809
13874
  readdirSync,
@@ -13811,6 +13876,7 @@ import {
13811
13876
  } from "node:fs";
13812
13877
  import { createGzip } from "node:zlib";
13813
13878
  import { Readable } from "node:stream";
13879
+ import * as esbuild from "esbuild";
13814
13880
  async function createBundle(projectDir, options) {
13815
13881
  const maxSize = options?.maxBundleSize ?? MAX_BUNDLE_SIZE_BYTES;
13816
13882
  let duraManifest;
@@ -13822,25 +13888,29 @@ async function createBundle(projectDir, options) {
13822
13888
  error: `Failed to read dura.json: ${err instanceof Error ? err.message : String(err)}`
13823
13889
  };
13824
13890
  }
13825
- const buildDir = join5(projectDir, ".dura", "build");
13826
- if (existsSync4(buildDir)) {
13891
+ const buildDir = join6(projectDir, ".dura", "build");
13892
+ if (existsSync5(buildDir)) {
13827
13893
  rmSync(buildDir, { recursive: true });
13828
13894
  }
13829
13895
  mkdirSync3(buildDir, { recursive: true });
13830
13896
  for (const auto of duraManifest.automations) {
13831
13897
  const entryPath = resolve2(projectDir, auto.entrypoint);
13832
- if (!existsSync4(entryPath)) {
13898
+ if (!existsSync5(entryPath)) {
13833
13899
  rmSync(buildDir, { recursive: true });
13834
13900
  return {
13835
13901
  success: false,
13836
13902
  error: `Entry point not found: ${auto.entrypoint}`
13837
13903
  };
13838
13904
  }
13839
- const outFile = join5(buildDir, `${auto.name}.js`);
13905
+ const outFile = join6(buildDir, `${auto.name}.js`);
13840
13906
  try {
13841
- const source = readFileSync4(entryPath, "utf-8");
13842
- const jsSource = stripTypeAnnotations(source);
13843
- writeFileSync5(outFile, jsSource);
13907
+ const source = readFileSync5(entryPath, "utf-8");
13908
+ const xformed = await esbuild.transform(source, {
13909
+ loader: "ts",
13910
+ target: "es2022",
13911
+ sourcemap: false
13912
+ });
13913
+ writeFileSync5(outFile, xformed.code);
13844
13914
  } catch (err) {
13845
13915
  rmSync(buildDir, { recursive: true });
13846
13916
  return {
@@ -13850,7 +13920,7 @@ async function createBundle(projectDir, options) {
13850
13920
  }
13851
13921
  }
13852
13922
  const deployManifest = generateDeploymentManifest(projectDir, duraManifest);
13853
- writeFileSync5(join5(buildDir, "manifest.json"), JSON.stringify(deployManifest, null, 2));
13923
+ writeFileSync5(join6(buildDir, "manifest.json"), JSON.stringify(deployManifest, null, 2));
13854
13924
  const archiveBuffer = await createTarGz(buildDir);
13855
13925
  if (archiveBuffer.length > maxSize) {
13856
13926
  rmSync(buildDir, { recursive: true });
@@ -13867,11 +13937,6 @@ async function createBundle(projectDir, options) {
13867
13937
  sizeBytes: archiveBuffer.length
13868
13938
  };
13869
13939
  }
13870
- function stripTypeAnnotations(source) {
13871
- let result = source.replace(/^import\s+type\s+.*$/gm, "");
13872
- result = result.replace(/:\s*(?:string|number|boolean|void|unknown|any|never|Promise<[^>]*>|Record<[^>]*>|Array<[^>]*>|[A-Z][a-zA-Z]*(?:<[^>]*>)?)\s*(?=[,)\]=;{])/g, "");
13873
- return result;
13874
- }
13875
13940
  async function createTarGz(dir) {
13876
13941
  const files = collectFiles(dir, dir);
13877
13942
  const tarBuffer = createTarBuffer(files);
@@ -13898,12 +13963,12 @@ function collectFiles(dir, base) {
13898
13963
  const result = [];
13899
13964
  const entries = readdirSync(dir, { withFileTypes: true });
13900
13965
  for (const entry of entries) {
13901
- const fullPath = join5(dir, entry.name);
13966
+ const fullPath = join6(dir, entry.name);
13902
13967
  if (entry.isFile()) {
13903
13968
  const relativePath = fullPath.slice(base.length + 1);
13904
13969
  result.push({
13905
13970
  name: relativePath,
13906
- content: new Uint8Array(readFileSync4(fullPath))
13971
+ content: new Uint8Array(readFileSync5(fullPath))
13907
13972
  });
13908
13973
  } else if (entry.isDirectory()) {
13909
13974
  result.push(...collectFiles(fullPath, base));
@@ -14212,8 +14277,13 @@ function parseFilters(filter) {
14212
14277
  return params;
14213
14278
  }
14214
14279
  function registerLogsCommand(program2) {
14215
- program2.command("logs [executionId]").description("View execution logs").requiredOption("--project <id>", "Project ID").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").option("--limit <n>", "Number of entries", "50").option("--filter <filter>", "Filter expression (e.g., 'level=error automation=my-auto')").option("--last <duration>", "Show logs from last duration (e.g., 1h, 30m, 7d)").option("--order <order>", "Sort order: asc or desc", "desc").option("-f, --follow", "Stream logs in real time (live tail)").action(async (executionId, opts, cmd) => {
14280
+ program2.command("logs [executionId]").description("View execution logs").option("--project <id>", "Project ID (defaults to dura.json)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").option("--limit <n>", "Number of entries", "50").option("--filter <filter>", "Filter expression (e.g., 'level=error automation=my-auto')").option("--last <duration>", "Show logs from last duration (e.g., 1h, 30m, 7d)").option("--order <order>", "Sort order: asc or desc", "desc").option("-f, --follow", "Stream logs in real time (live tail)").action(async (executionId, opts, cmd) => {
14216
14281
  const output = getOutput(cmd);
14282
+ const projectId = resolveProjectId({ project: opts.project });
14283
+ if (!projectId) {
14284
+ output.error("PROJECT_REQUIRED", "No project ID. Use --project or run this command inside a dura.json project.");
14285
+ return;
14286
+ }
14217
14287
  const token = opts.token || getAuthToken();
14218
14288
  if (!token) {
14219
14289
  output.error("AUTH_REQUIRED", "Not logged in", "Run: dura login");
@@ -14234,10 +14304,11 @@ function registerLogsCommand(program2) {
14234
14304
  }
14235
14305
  if (executionId)
14236
14306
  filters.executionId = executionId;
14237
- const wsUrl = buildWsUrl(apiUrl, opts.project, filters, token);
14307
+ const wsUrl = buildWsUrl(apiUrl, projectId, filters, token);
14238
14308
  if (!output.isJson()) {
14239
- output.info(`Connecting to live log stream for project ${opts.project}…`);
14309
+ output.info(`Connecting to live log stream for project ${projectId}…`);
14240
14310
  }
14311
+ const resolvedProjectId = projectId;
14241
14312
  await new Promise((resolve3) => {
14242
14313
  const MAX_RETRIES = 5;
14243
14314
  let retries = 0;
@@ -14248,7 +14319,7 @@ function registerLogsCommand(program2) {
14248
14319
  currentClient?.disconnect();
14249
14320
  });
14250
14321
  function connect() {
14251
- currentClient = createFollowClient(wsUrl, opts.project, filters, {
14322
+ currentClient = createFollowClient(wsUrl, resolvedProjectId, filters, {
14252
14323
  wsFactory: (url) => new WebSocket(url),
14253
14324
  onEntry: (entry) => {
14254
14325
  if (output.isJson()) {
@@ -14317,7 +14388,7 @@ function registerLogsCommand(program2) {
14317
14388
  }
14318
14389
  query["start"] = new Date(Date.now() - durationMs).toISOString();
14319
14390
  }
14320
- const result = await client.get(`/api/v1/projects/${opts.project}/logs`, query);
14391
+ const result = await client.get(`/api/v1/projects/${projectId}/logs`, query);
14321
14392
  output.table(["Timestamp", "Level", "Automation", "Message"], result.entries.map((e) => [
14322
14393
  e.timestamp,
14323
14394
  e.level,
@@ -14328,7 +14399,7 @@ function registerLogsCommand(program2) {
14328
14399
  output.info(`Total: ${result.totalCount} entries`);
14329
14400
  }
14330
14401
  } else if (executionId) {
14331
- const execution = await client.get(`/api/v1/projects/${opts.project}/executions/${executionId}`);
14402
+ const execution = await client.get(`/api/v1/projects/${projectId}/executions/${executionId}`);
14332
14403
  if ((execution.status === "failed" || execution.status === "timeout") && (execution.errorName || execution.errorMessage)) {
14333
14404
  if (output.isJson()) {
14334
14405
  output.success({
@@ -14359,10 +14430,10 @@ Error: ${name}
14359
14430
  `);
14360
14431
  }
14361
14432
  }
14362
- const logs = await client.get(`/api/v1/projects/${opts.project}/executions/${executionId}/logs`);
14433
+ const logs = await client.get(`/api/v1/projects/${projectId}/executions/${executionId}/logs`);
14363
14434
  output.table(["Timestamp", "Level", "Message"], logs.map((l) => [l.timestamp, l.level, l.message]));
14364
14435
  } else {
14365
- const executions = await client.get(`/api/v1/projects/${opts.project}/executions`, { limit: opts.limit });
14436
+ const executions = await client.get(`/api/v1/projects/${projectId}/executions`, { limit: opts.limit });
14366
14437
  output.table(["ID", "Status", "Trigger", "Duration", "Created"], executions.map((e) => [
14367
14438
  e.id,
14368
14439
  e.status,
@@ -14384,6 +14455,7 @@ var init_logs = __esm(() => {
14384
14455
  init_src3();
14385
14456
  init_api_client();
14386
14457
  init_config_store();
14458
+ init_project_id();
14387
14459
  });
14388
14460
 
14389
14461
  // src/commands/run.ts
@@ -14445,15 +14517,20 @@ function getClient(opts) {
14445
14517
  }
14446
14518
  function registerScheduleCommand(program2) {
14447
14519
  const schedule = program2.command("schedule").description("Manage cron schedules for automations");
14448
- schedule.command("set").description("Create or update a cron schedule").requiredOption("--cron <expression>", "Cron expression (e.g. '0 9 * * *')").requiredOption("--automation <name>", "Automation name").requiredOption("--project <id>", "Project ID").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
14520
+ schedule.command("set").description("Create or update a cron schedule").requiredOption("--cron <expression>", "Cron expression (e.g. '0 9 * * *')").requiredOption("--automation <name>", "Automation name").option("--project <id>", "Project ID (defaults to dura.json)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
14449
14521
  const output = getOutput(cmd);
14522
+ const projectId = resolveProjectId({ project: opts.project });
14523
+ if (!projectId) {
14524
+ output.error("PROJECT_REQUIRED", "No project ID. Use --project or run this command inside a dura.json project.");
14525
+ return;
14526
+ }
14450
14527
  const auth = getClient(opts);
14451
14528
  if (!auth) {
14452
14529
  output.error("AUTH_REQUIRED", "Not logged in", "Run: dura login");
14453
14530
  return;
14454
14531
  }
14455
14532
  try {
14456
- const result = await auth.client.post(`/api/v1/projects/${opts.project}/schedules`, {
14533
+ const result = await auth.client.post(`/api/v1/projects/${projectId}/schedules`, {
14457
14534
  automationName: opts.automation,
14458
14535
  cron: opts.cron
14459
14536
  });
@@ -14466,15 +14543,20 @@ function registerScheduleCommand(program2) {
14466
14543
  }
14467
14544
  }
14468
14545
  });
14469
- schedule.command("list").description("List cron schedules for a project").requiredOption("--project <id>", "Project ID").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
14546
+ schedule.command("list").description("List cron schedules for a project").option("--project <id>", "Project ID (defaults to dura.json)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
14470
14547
  const output = getOutput(cmd);
14548
+ const projectId = resolveProjectId({ project: opts.project });
14549
+ if (!projectId) {
14550
+ output.error("PROJECT_REQUIRED", "No project ID. Use --project or run this command inside a dura.json project.");
14551
+ return;
14552
+ }
14471
14553
  const auth = getClient(opts);
14472
14554
  if (!auth) {
14473
14555
  output.error("AUTH_REQUIRED", "Not logged in", "Run: dura login");
14474
14556
  return;
14475
14557
  }
14476
14558
  try {
14477
- const result = await auth.client.get(`/api/v1/projects/${opts.project}/schedules`);
14559
+ const result = await auth.client.get(`/api/v1/projects/${projectId}/schedules`);
14478
14560
  output.success(result);
14479
14561
  } catch (err) {
14480
14562
  if (err instanceof ApiClientError) {
@@ -14484,19 +14566,24 @@ function registerScheduleCommand(program2) {
14484
14566
  }
14485
14567
  }
14486
14568
  });
14487
- schedule.command("remove <schedule-id>").description("Remove a cron schedule").requiredOption("--project <id>", "Project ID").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").option("--confirm", "Confirm the destructive delete operation").action(async (scheduleId, opts, cmd) => {
14569
+ schedule.command("remove <schedule-id>").description("Remove a cron schedule").option("--project <id>", "Project ID (defaults to dura.json)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").option("--confirm", "Confirm the destructive delete operation").action(async (scheduleId, opts, cmd) => {
14488
14570
  const output = getOutput(cmd);
14489
14571
  if (!opts.confirm) {
14490
14572
  output.error("CONFIRM_REQUIRED", "This will permanently delete the schedule. Add --confirm to proceed.");
14491
14573
  return;
14492
14574
  }
14575
+ const projectId = resolveProjectId({ project: opts.project });
14576
+ if (!projectId) {
14577
+ output.error("PROJECT_REQUIRED", "No project ID. Use --project or run this command inside a dura.json project.");
14578
+ return;
14579
+ }
14493
14580
  const auth = getClient(opts);
14494
14581
  if (!auth) {
14495
14582
  output.error("AUTH_REQUIRED", "Not logged in", "Run: dura login");
14496
14583
  return;
14497
14584
  }
14498
14585
  try {
14499
- const result = await auth.client.delete(`/api/v1/projects/${opts.project}/schedules/${scheduleId}`);
14586
+ const result = await auth.client.delete(`/api/v1/projects/${projectId}/schedules/${scheduleId}`);
14500
14587
  output.success(result);
14501
14588
  } catch (err) {
14502
14589
  if (err instanceof ApiClientError) {
@@ -14511,6 +14598,7 @@ var init_schedule = __esm(() => {
14511
14598
  init_src3();
14512
14599
  init_api_client();
14513
14600
  init_config_store();
14601
+ init_project_id();
14514
14602
  });
14515
14603
 
14516
14604
  // src/dev/server.ts
@@ -15043,8 +15131,8 @@ __export(exports_file_watcher, {
15043
15131
  resolveAffectedAutomation: () => resolveAffectedAutomation,
15044
15132
  createFileWatcher: () => createFileWatcher
15045
15133
  });
15046
- import { watch, existsSync as existsSync5 } from "node:fs";
15047
- import { join as join6 } from "node:path";
15134
+ import { watch, existsSync as existsSync6 } from "node:fs";
15135
+ import { join as join7 } from "node:path";
15048
15136
  function resolveAffectedAutomation(manifest, changedPath) {
15049
15137
  const normalized = changedPath.replace(/\\/g, "/");
15050
15138
  const directMatch = manifest.automations.filter((a) => a.entrypoint.replace(/\\/g, "/") === normalized);
@@ -15063,7 +15151,7 @@ function createFileWatcher(options) {
15063
15151
  return;
15064
15152
  if (!/\.(ts|tsx|js|jsx|mjs|mts)$/.test(filename))
15065
15153
  return;
15066
- const relativePath = join6(dir, filename).replace(/\\/g, "/");
15154
+ const relativePath = join7(dir, filename).replace(/\\/g, "/");
15067
15155
  const existing = debounceTimers.get(relativePath);
15068
15156
  if (existing) {
15069
15157
  clearTimeout(existing);
@@ -15079,8 +15167,8 @@ function createFileWatcher(options) {
15079
15167
  return;
15080
15168
  watching = true;
15081
15169
  for (const dir of watchDirs) {
15082
- const fullDir = join6(projectDir, dir);
15083
- if (!existsSync5(fullDir))
15170
+ const fullDir = join7(projectDir, dir);
15171
+ if (!existsSync6(fullDir))
15084
15172
  continue;
15085
15173
  try {
15086
15174
  const fsWatcher = watch(fullDir, { recursive: true }, (_eventType, filename) => {
@@ -15179,12 +15267,12 @@ function registerDevCommand(program2) {
15179
15267
  }
15180
15268
  throw err;
15181
15269
  }
15182
- const { existsSync: existsSync6, readFileSync: readFileSync5 } = await import("node:fs");
15183
- const { join: join7 } = await import("node:path");
15184
- const envPath = join7(projectDir, ".env.local");
15270
+ const { existsSync: existsSync7, readFileSync: readFileSync6 } = await import("node:fs");
15271
+ const { join: join8 } = await import("node:path");
15272
+ const envPath = join8(projectDir, ".env.local");
15185
15273
  let secrets2 = {};
15186
- if (existsSync6(envPath)) {
15187
- const content = readFileSync5(envPath, "utf-8");
15274
+ if (existsSync7(envPath)) {
15275
+ const content = readFileSync6(envPath, "utf-8");
15188
15276
  secrets2 = parseEnvFile(content);
15189
15277
  output.info(`Loaded ${Object.keys(secrets2).length} secret(s) from .env.local`);
15190
15278
  }
@@ -15206,6 +15294,14 @@ function registerDevCommand(program2) {
15206
15294
  }
15207
15295
  }
15208
15296
  }
15297
+ if (typeof globalThis.Bun === "undefined") {
15298
+ try {
15299
+ const { register } = await import("tsx/esm/api");
15300
+ register();
15301
+ } catch (err) {
15302
+ output.warn(`Failed to register tsx loader — .ts handlers may fail to load. ${err instanceof Error ? err.message : ""}`);
15303
+ }
15304
+ }
15209
15305
  const { createLocalExecutor: createLocalExecutor2 } = await Promise.resolve().then(() => (init_executor(), exports_executor));
15210
15306
  const { installDevGlobal: installDevGlobal2 } = await Promise.resolve().then(() => exports_dev_globals);
15211
15307
  const executor = createLocalExecutor2({ projectDir });
@@ -15526,14 +15622,14 @@ var init_comparator = __esm(() => {
15526
15622
 
15527
15623
  // src/snapshot/file-manager.ts
15528
15624
  import {
15529
- existsSync as existsSync6,
15625
+ existsSync as existsSync7,
15530
15626
  mkdirSync as mkdirSync4,
15531
- readFileSync as readFileSync5,
15627
+ readFileSync as readFileSync6,
15532
15628
  writeFileSync as writeFileSync6,
15533
15629
  readdirSync as readdirSync2,
15534
15630
  unlinkSync
15535
15631
  } from "node:fs";
15536
- import { join as join7 } from "node:path";
15632
+ import { join as join8 } from "node:path";
15537
15633
  function automationNameToFileName(automationName) {
15538
15634
  return automationName.replace(/\//g, "-") + ".snap.json";
15539
15635
  }
@@ -15544,22 +15640,22 @@ function fileNameToAutomationName(fileName) {
15544
15640
  class SnapshotFileManager {
15545
15641
  snapshotsDir;
15546
15642
  constructor(projectDir) {
15547
- this.snapshotsDir = join7(projectDir, SNAPSHOTS_DIR);
15643
+ this.snapshotsDir = join8(projectDir, SNAPSHOTS_DIR);
15548
15644
  }
15549
15645
  filePath(automationName) {
15550
- return join7(this.snapshotsDir, automationNameToFileName(automationName));
15646
+ return join8(this.snapshotsDir, automationNameToFileName(automationName));
15551
15647
  }
15552
15648
  ensureDir() {
15553
- if (!existsSync6(this.snapshotsDir)) {
15649
+ if (!existsSync7(this.snapshotsDir)) {
15554
15650
  mkdirSync4(this.snapshotsDir, { recursive: true });
15555
15651
  }
15556
15652
  }
15557
15653
  read(automationName) {
15558
15654
  const path = this.filePath(automationName);
15559
- if (!existsSync6(path))
15655
+ if (!existsSync7(path))
15560
15656
  return null;
15561
15657
  try {
15562
- const raw = readFileSync5(path, "utf-8");
15658
+ const raw = readFileSync6(path, "utf-8");
15563
15659
  return JSON.parse(raw);
15564
15660
  } catch {
15565
15661
  return null;
@@ -15589,14 +15685,14 @@ class SnapshotFileManager {
15589
15685
  });
15590
15686
  }
15591
15687
  listAutomationNames() {
15592
- if (!existsSync6(this.snapshotsDir))
15688
+ if (!existsSync7(this.snapshotsDir))
15593
15689
  return [];
15594
15690
  const files = readdirSync2(this.snapshotsDir).filter((f) => f.endsWith(".snap.json"));
15595
15691
  const names = [];
15596
15692
  for (const file of files) {
15597
- const path = join7(this.snapshotsDir, file);
15693
+ const path = join8(this.snapshotsDir, file);
15598
15694
  try {
15599
- const raw = readFileSync5(path, "utf-8");
15695
+ const raw = readFileSync6(path, "utf-8");
15600
15696
  const parsed = JSON.parse(raw);
15601
15697
  if (parsed.automationName) {
15602
15698
  names.push(parsed.automationName);
@@ -15607,7 +15703,7 @@ class SnapshotFileManager {
15607
15703
  }
15608
15704
  delete(automationName) {
15609
15705
  const path = this.filePath(automationName);
15610
- if (existsSync6(path)) {
15706
+ if (existsSync7(path)) {
15611
15707
  unlinkSync(path);
15612
15708
  }
15613
15709
  }
@@ -15616,25 +15712,25 @@ var SNAPSHOTS_DIR = "__snapshots__";
15616
15712
  var init_file_manager = () => {};
15617
15713
 
15618
15714
  // src/snapshot/config-reader.ts
15619
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
15620
- import { join as join8 } from "node:path";
15715
+ import { existsSync as existsSync8, readFileSync as readFileSync7 } from "node:fs";
15716
+ import { join as join9 } from "node:path";
15621
15717
  function readSnapshotConfig(projectDir) {
15622
- const durajsonPath = join8(projectDir, "dura.json");
15623
- const testjsonPath = join8(projectDir, "dura.test.json");
15718
+ const durajsonPath = join9(projectDir, "dura.json");
15719
+ const testjsonPath = join9(projectDir, "dura.test.json");
15624
15720
  let fromDuraJson = {};
15625
15721
  let fromTestJson = {};
15626
15722
  let mocks;
15627
- if (existsSync7(durajsonPath)) {
15723
+ if (existsSync8(durajsonPath)) {
15628
15724
  try {
15629
- const raw = JSON.parse(readFileSync6(durajsonPath, "utf-8"));
15725
+ const raw = JSON.parse(readFileSync7(durajsonPath, "utf-8"));
15630
15726
  if (raw["snapshots"] && typeof raw["snapshots"] === "object" && !Array.isArray(raw["snapshots"])) {
15631
15727
  fromDuraJson = raw["snapshots"];
15632
15728
  }
15633
15729
  } catch {}
15634
15730
  }
15635
- if (existsSync7(testjsonPath)) {
15731
+ if (existsSync8(testjsonPath)) {
15636
15732
  try {
15637
- const raw = JSON.parse(readFileSync6(testjsonPath, "utf-8"));
15733
+ const raw = JSON.parse(readFileSync7(testjsonPath, "utf-8"));
15638
15734
  if (raw["snapshots"] && typeof raw["snapshots"] === "object" && !Array.isArray(raw["snapshots"])) {
15639
15735
  fromTestJson = raw["snapshots"];
15640
15736
  }
@@ -16199,24 +16295,29 @@ __export(exports_domains, {
16199
16295
  registerDomainsCommand: () => registerDomainsCommand
16200
16296
  });
16201
16297
  function resolveAuth2(opts, output) {
16298
+ const projectId = resolveProjectId({ project: opts.project });
16299
+ if (!projectId) {
16300
+ output.error("PROJECT_REQUIRED", "No project ID. Use --project or run this command inside a dura.json project.");
16301
+ return null;
16302
+ }
16202
16303
  const token = opts.token || getAuthToken();
16203
16304
  if (!token) {
16204
16305
  output.error("AUTH_REQUIRED", "Not logged in", "Run: dura login");
16205
16306
  return null;
16206
16307
  }
16207
16308
  const apiUrl = opts.apiUrl || getApiUrl();
16208
- return { client: new ApiClient(apiUrl, token) };
16309
+ return { client: new ApiClient(apiUrl, token), projectId };
16209
16310
  }
16210
16311
  function registerDomainsCommand(program2) {
16211
16312
  const domains = program2.command("domains").description("Manage custom domains for a project");
16212
- domains.command("add <domain>").description("Add a custom domain").requiredOption("--project <id>", "Project ID").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (domain, opts, cmd) => {
16313
+ domains.command("add <domain>").description("Add a custom domain").option("--project <id>", "Project ID (defaults to dura.json)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (domain, opts, cmd) => {
16213
16314
  const output = getOutput(cmd);
16214
16315
  const auth = resolveAuth2(opts, output);
16215
16316
  if (!auth)
16216
16317
  return;
16217
- const { client } = auth;
16318
+ const { client, projectId } = auth;
16218
16319
  try {
16219
- const result = await client.post(`/api/v1/projects/${opts.project}/domains`, { domain });
16320
+ const result = await client.post(`/api/v1/projects/${projectId}/domains`, { domain });
16220
16321
  output.success(result);
16221
16322
  if (!output.isJson()) {
16222
16323
  output.info(`
@@ -16224,7 +16325,7 @@ Add this TXT record to your DNS:
16224
16325
  ` + ` Host: _dura-verification.${domain}
16225
16326
  ` + ` Value: ${result.txtRecord}
16226
16327
  ` + `
16227
- Then run: dura domains verify ${result.id} --project ${opts.project}`);
16328
+ Then run: dura domains verify ${result.id} --project ${projectId}`);
16228
16329
  }
16229
16330
  } catch (err) {
16230
16331
  if (err instanceof Error) {
@@ -16232,14 +16333,14 @@ Then run: dura domains verify ${result.id} --project ${opts.project}`);
16232
16333
  }
16233
16334
  }
16234
16335
  });
16235
- domains.command("list").description("List custom domains").requiredOption("--project <id>", "Project ID").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
16336
+ domains.command("list").description("List custom domains").option("--project <id>", "Project ID (defaults to dura.json)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
16236
16337
  const output = getOutput(cmd);
16237
16338
  const auth = resolveAuth2(opts, output);
16238
16339
  if (!auth)
16239
16340
  return;
16240
- const { client } = auth;
16341
+ const { client, projectId } = auth;
16241
16342
  try {
16242
- const result = await client.get(`/api/v1/projects/${opts.project}/domains`);
16343
+ const result = await client.get(`/api/v1/projects/${projectId}/domains`);
16243
16344
  output.table(["Domain", "Status", "Verified", "Created"], result.map((d) => [
16244
16345
  d.domain,
16245
16346
  d.status,
@@ -16252,7 +16353,7 @@ Then run: dura domains verify ${result.id} --project ${opts.project}`);
16252
16353
  }
16253
16354
  }
16254
16355
  });
16255
- domains.command("remove <domainId>").description("Remove a custom domain").requiredOption("--project <id>", "Project ID").option("--confirm", "Confirm this destructive operation").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (domainId, opts, cmd) => {
16356
+ domains.command("remove <domainId>").description("Remove a custom domain").option("--project <id>", "Project ID (defaults to dura.json)").option("--confirm", "Confirm this destructive operation").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (domainId, opts, cmd) => {
16256
16357
  const output = getOutput(cmd);
16257
16358
  if (!opts.confirm) {
16258
16359
  output.error("CONFIRM_REQUIRED", "This is a destructive operation. Re-run with --confirm to proceed.");
@@ -16261,9 +16362,9 @@ Then run: dura domains verify ${result.id} --project ${opts.project}`);
16261
16362
  const auth = resolveAuth2(opts, output);
16262
16363
  if (!auth)
16263
16364
  return;
16264
- const { client } = auth;
16365
+ const { client, projectId } = auth;
16265
16366
  try {
16266
- await client.delete(`/api/v1/projects/${opts.project}/domains/${domainId}`);
16367
+ await client.delete(`/api/v1/projects/${projectId}/domains/${domainId}`);
16267
16368
  output.success({ deleted: true, domainId });
16268
16369
  } catch (err) {
16269
16370
  if (err instanceof Error) {
@@ -16271,14 +16372,14 @@ Then run: dura domains verify ${result.id} --project ${opts.project}`);
16271
16372
  }
16272
16373
  }
16273
16374
  });
16274
- domains.command("verify <domainId>").description("Verify a domain's DNS TXT record").requiredOption("--project <id>", "Project ID").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (domainId, opts, cmd) => {
16375
+ domains.command("verify <domainId>").description("Verify a domain's DNS TXT record").option("--project <id>", "Project ID (defaults to dura.json)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (domainId, opts, cmd) => {
16275
16376
  const output = getOutput(cmd);
16276
16377
  const auth = resolveAuth2(opts, output);
16277
16378
  if (!auth)
16278
16379
  return;
16279
- const { client } = auth;
16380
+ const { client, projectId } = auth;
16280
16381
  try {
16281
- const result = await client.post(`/api/v1/projects/${opts.project}/domains/${domainId}/verify`, {});
16382
+ const result = await client.post(`/api/v1/projects/${projectId}/domains/${domainId}/verify`, {});
16282
16383
  output.success(result);
16283
16384
  } catch (err) {
16284
16385
  if (err instanceof Error) {
@@ -16291,6 +16392,7 @@ var init_domains = __esm(() => {
16291
16392
  init_src3();
16292
16393
  init_api_client();
16293
16394
  init_config_store();
16395
+ init_project_id();
16294
16396
  });
16295
16397
 
16296
16398
  // src/commands/endpoint-keys.ts
@@ -16299,24 +16401,29 @@ __export(exports_endpoint_keys, {
16299
16401
  registerEndpointKeysCommand: () => registerEndpointKeysCommand
16300
16402
  });
16301
16403
  function resolveAuth3(opts, output) {
16404
+ const projectId = resolveProjectId({ project: opts.project });
16405
+ if (!projectId) {
16406
+ output.error("PROJECT_REQUIRED", "No project ID. Use --project or run this command inside a dura.json project.");
16407
+ return null;
16408
+ }
16302
16409
  const token = opts.token || getAuthToken();
16303
16410
  if (!token) {
16304
16411
  output.error("AUTH_REQUIRED", "Not logged in", "Run: dura login");
16305
16412
  return null;
16306
16413
  }
16307
16414
  const apiUrl = opts.apiUrl || getApiUrl();
16308
- return { client: new ApiClient(apiUrl, token) };
16415
+ return { client: new ApiClient(apiUrl, token), projectId };
16309
16416
  }
16310
16417
  function registerEndpointKeysCommand(program2) {
16311
16418
  const keys = program2.command("endpoint-keys").description("Manage per-project endpoint keys for automation auth");
16312
- keys.command("create <name>").description("Create a new endpoint key").requiredOption("--project <id>", "Project ID").option("--expires <date>", "Expiration date (ISO 8601)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (name, opts, cmd) => {
16419
+ keys.command("create <name>").description("Create a new endpoint key").option("--project <id>", "Project ID (defaults to dura.json)").option("--expires <date>", "Expiration date (ISO 8601)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (name, opts, cmd) => {
16313
16420
  const output = getOutput(cmd);
16314
16421
  const auth = resolveAuth3(opts, output);
16315
16422
  if (!auth)
16316
16423
  return;
16317
- const { client } = auth;
16424
+ const { client, projectId } = auth;
16318
16425
  try {
16319
- const result = await client.post(`/api/v1/projects/${opts.project}/endpoint-keys`, {
16426
+ const result = await client.post(`/api/v1/projects/${projectId}/endpoint-keys`, {
16320
16427
  name,
16321
16428
  expiresAt: opts.expires
16322
16429
  });
@@ -16330,14 +16437,14 @@ function registerEndpointKeysCommand(program2) {
16330
16437
  }
16331
16438
  }
16332
16439
  });
16333
- keys.command("list").description("List endpoint keys for a project").requiredOption("--project <id>", "Project ID").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
16440
+ keys.command("list").description("List endpoint keys for a project").option("--project <id>", "Project ID (defaults to dura.json)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
16334
16441
  const output = getOutput(cmd);
16335
16442
  const auth = resolveAuth3(opts, output);
16336
16443
  if (!auth)
16337
16444
  return;
16338
- const { client } = auth;
16445
+ const { client, projectId } = auth;
16339
16446
  try {
16340
- const result = await client.get(`/api/v1/projects/${opts.project}/endpoint-keys`);
16447
+ const result = await client.get(`/api/v1/projects/${projectId}/endpoint-keys`);
16341
16448
  output.table(["Name", "Prefix", "Status", "Expires", "Last Used", "Created"], result.map((k) => [
16342
16449
  k.name,
16343
16450
  k.keyPrefix,
@@ -16352,7 +16459,7 @@ function registerEndpointKeysCommand(program2) {
16352
16459
  }
16353
16460
  }
16354
16461
  });
16355
- keys.command("revoke <keyId>").description("Revoke an endpoint key").requiredOption("--project <id>", "Project ID").option("--confirm", "Confirm this destructive operation").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (keyId, opts, cmd) => {
16462
+ keys.command("revoke <keyId>").description("Revoke an endpoint key").option("--project <id>", "Project ID (defaults to dura.json)").option("--confirm", "Confirm this destructive operation").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (keyId, opts, cmd) => {
16356
16463
  const output = getOutput(cmd);
16357
16464
  if (!opts.confirm) {
16358
16465
  output.error("CONFIRM_REQUIRED", "This is a destructive operation. Re-run with --confirm to proceed.");
@@ -16361,9 +16468,9 @@ function registerEndpointKeysCommand(program2) {
16361
16468
  const auth = resolveAuth3(opts, output);
16362
16469
  if (!auth)
16363
16470
  return;
16364
- const { client } = auth;
16471
+ const { client, projectId } = auth;
16365
16472
  try {
16366
- await client.post(`/api/v1/projects/${opts.project}/endpoint-keys/${keyId}/revoke`, {});
16473
+ await client.post(`/api/v1/projects/${projectId}/endpoint-keys/${keyId}/revoke`, {});
16367
16474
  output.success({ revoked: true, keyId });
16368
16475
  } catch (err) {
16369
16476
  if (err instanceof Error) {
@@ -16376,6 +16483,7 @@ var init_endpoint_keys2 = __esm(() => {
16376
16483
  init_src3();
16377
16484
  init_api_client();
16378
16485
  init_config_store();
16486
+ init_project_id();
16379
16487
  });
16380
16488
 
16381
16489
  // src/commands/usage.ts
@@ -16622,19 +16730,19 @@ __export(exports_export, {
16622
16730
  collectExportFiles: () => collectExportFiles
16623
16731
  });
16624
16732
  import {
16625
- existsSync as existsSync8,
16626
- readFileSync as readFileSync7,
16733
+ existsSync as existsSync9,
16734
+ readFileSync as readFileSync8,
16627
16735
  readdirSync as readdirSync3,
16628
16736
  statSync,
16629
16737
  writeFileSync as writeFileSync7
16630
16738
  } from "node:fs";
16631
- import { join as join9, relative, resolve as resolve4 } from "node:path";
16739
+ import { join as join10, relative, resolve as resolve4 } from "node:path";
16632
16740
  function collectExportFiles(baseDir, currentDir) {
16633
16741
  const dir = currentDir ?? baseDir;
16634
16742
  const entries = [];
16635
16743
  const items = readdirSync3(dir);
16636
16744
  for (const item of items) {
16637
- const fullPath = join9(dir, item);
16745
+ const fullPath = join10(dir, item);
16638
16746
  const relPath = relative(baseDir, fullPath);
16639
16747
  const stat = statSync(fullPath);
16640
16748
  if (stat.isDirectory()) {
@@ -16643,7 +16751,7 @@ function collectExportFiles(baseDir, currentDir) {
16643
16751
  }
16644
16752
  } else if (stat.isFile()) {
16645
16753
  if (!EXCLUDED_FILES.has(item)) {
16646
- const content = readFileSync7(fullPath, "utf-8");
16754
+ const content = readFileSync8(fullPath, "utf-8");
16647
16755
  entries.push({ relativePath: relPath, content });
16648
16756
  }
16649
16757
  }
@@ -16667,14 +16775,14 @@ function registerExportCommand(program2) {
16667
16775
  program2.command("export").description("Export project source + config into a portable archive").option("--dir <path>", "Project directory", ".").option("--output <path>", "Output file path").action(async (opts, cmd) => {
16668
16776
  const output = getOutput(cmd);
16669
16777
  const projectDir = resolve4(opts.dir ?? ".");
16670
- const manifestPath = join9(projectDir, "dura.json");
16671
- if (!existsSync8(manifestPath)) {
16778
+ const manifestPath = join10(projectDir, "dura.json");
16779
+ if (!existsSync9(manifestPath)) {
16672
16780
  output.error("EXPORT_NO_MANIFEST", "No dura.json found in project directory", "Run this command from a dura project directory or use --dir");
16673
16781
  return;
16674
16782
  }
16675
16783
  let projectName;
16676
16784
  try {
16677
- const manifestContent = readFileSync7(manifestPath, "utf-8");
16785
+ const manifestContent = readFileSync8(manifestPath, "utf-8");
16678
16786
  const manifest = JSON.parse(manifestContent);
16679
16787
  projectName = manifest.name ?? "unnamed-project";
16680
16788
  } catch {
@@ -16688,7 +16796,7 @@ function registerExportCommand(program2) {
16688
16796
  return;
16689
16797
  }
16690
16798
  const archive = createArchive(projectName, files);
16691
- const outputPath = opts.output ?? join9(projectDir, `${projectName}-export.json`);
16799
+ const outputPath = opts.output ?? join10(projectDir, `${projectName}-export.json`);
16692
16800
  try {
16693
16801
  writeFileSync7(outputPath, archive, "utf-8");
16694
16802
  } catch (err) {
@@ -16724,8 +16832,13 @@ __export(exports_replay, {
16724
16832
  registerReplayCommand: () => registerReplayCommand
16725
16833
  });
16726
16834
  function registerReplayCommand(program2) {
16727
- program2.command("replay <execution-id>").description("Replay an execution with original inputs (optionally with overrides)").requiredOption("--project <id>", "Project ID").option("--body <json>", "Override the request body as JSON").option("--dry-run", "Show payload without replaying").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (executionId, opts, cmd) => {
16835
+ program2.command("replay <execution-id>").description("Replay an execution with original inputs (optionally with overrides)").option("--project <id>", "Project ID (defaults to dura.json)").option("--body <json>", "Override the request body as JSON").option("--dry-run", "Show payload without replaying").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (executionId, opts, cmd) => {
16728
16836
  const output = getOutput(cmd);
16837
+ const projectId = resolveProjectId({ project: opts.project });
16838
+ if (!projectId) {
16839
+ output.error("PROJECT_REQUIRED", "No project ID. Use --project or run this command inside a dura.json project.");
16840
+ return;
16841
+ }
16729
16842
  const token = opts.token || getAuthToken();
16730
16843
  if (!token) {
16731
16844
  output.error("AUTH_REQUIRED", "Not logged in", "Run: dura login");
@@ -16735,7 +16848,7 @@ function registerReplayCommand(program2) {
16735
16848
  const client = new ApiClient(apiUrl, token);
16736
16849
  try {
16737
16850
  if (opts.dryRun) {
16738
- const payload = await client.get(`/api/v1/projects/${opts.project}/executions/${executionId}/payload`);
16851
+ const payload = await client.get(`/api/v1/projects/${projectId}/executions/${executionId}/payload`);
16739
16852
  output.success(payload);
16740
16853
  return;
16741
16854
  }
@@ -16754,7 +16867,7 @@ function registerReplayCommand(program2) {
16754
16867
  return;
16755
16868
  }
16756
16869
  }
16757
- const result = await client.post(`/api/v1/projects/${opts.project}/executions/${executionId}/replay`, { overrides });
16870
+ const result = await client.post(`/api/v1/projects/${projectId}/executions/${executionId}/replay`, { overrides });
16758
16871
  output.success(result);
16759
16872
  } catch (err) {
16760
16873
  if (err instanceof ApiClientError) {
@@ -16766,8 +16879,13 @@ function registerReplayCommand(program2) {
16766
16879
  });
16767
16880
  }
16768
16881
  function registerReplaysCommand(program2) {
16769
- program2.command("replays <execution-id>").description("List all replays of an execution").requiredOption("--project <id>", "Project ID").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (executionId, opts, cmd) => {
16882
+ program2.command("replays <execution-id>").description("List all replays of an execution").option("--project <id>", "Project ID (defaults to dura.json)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (executionId, opts, cmd) => {
16770
16883
  const output = getOutput(cmd);
16884
+ const projectId = resolveProjectId({ project: opts.project });
16885
+ if (!projectId) {
16886
+ output.error("PROJECT_REQUIRED", "No project ID. Use --project or run this command inside a dura.json project.");
16887
+ return;
16888
+ }
16771
16889
  const token = opts.token || getAuthToken();
16772
16890
  if (!token) {
16773
16891
  output.error("AUTH_REQUIRED", "Not logged in", "Run: dura login");
@@ -16776,7 +16894,7 @@ function registerReplaysCommand(program2) {
16776
16894
  const apiUrl = opts.apiUrl || getApiUrl();
16777
16895
  const client = new ApiClient(apiUrl, token);
16778
16896
  try {
16779
- const replays = await client.get(`/api/v1/projects/${opts.project}/executions/${executionId}/replays`);
16897
+ const replays = await client.get(`/api/v1/projects/${projectId}/executions/${executionId}/replays`);
16780
16898
  if (replays.length === 0) {
16781
16899
  if (!output.isJson()) {
16782
16900
  output.info("No replays found for this execution.");
@@ -16805,6 +16923,7 @@ var init_replay = __esm(() => {
16805
16923
  init_src3();
16806
16924
  init_api_client();
16807
16925
  init_config_store();
16926
+ init_project_id();
16808
16927
  });
16809
16928
 
16810
16929
  // src/commands/kv.ts
@@ -16813,29 +16932,34 @@ __export(exports_kv, {
16813
16932
  registerKvCommand: () => registerKvCommand
16814
16933
  });
16815
16934
  function resolveAuth4(opts, output) {
16935
+ const projectId = resolveProjectId({ project: opts.project });
16936
+ if (!projectId) {
16937
+ output.error("PROJECT_REQUIRED", "No project ID. Use --project or run this command inside a dura.json project.");
16938
+ return null;
16939
+ }
16816
16940
  const token = opts.token || getAuthToken();
16817
16941
  if (!token) {
16818
16942
  output.error("AUTH_REQUIRED", "Not logged in", "Run: dura login");
16819
16943
  return null;
16820
16944
  }
16821
16945
  const apiUrl = opts.apiUrl || getApiUrl();
16822
- return { client: new ApiClient(apiUrl, token) };
16946
+ return { client: new ApiClient(apiUrl, token), projectId };
16823
16947
  }
16824
16948
  function registerKvCommand(program2) {
16825
16949
  const kv = program2.command("kv").description("Manage the built-in per-project key-value store");
16826
- kv.command("list").description("List keys in the project KV namespace").requiredOption("--project <id>", "Project ID").option("--pattern <glob>", "Filter keys by glob pattern (e.g. user:*)").option("--limit <n>", "Maximum number of keys to return").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
16950
+ kv.command("list").description("List keys in the project KV namespace").option("--project <id>", "Project ID (defaults to dura.json)").option("--pattern <glob>", "Filter keys by glob pattern (e.g. user:*)").option("--limit <n>", "Maximum number of keys to return").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
16827
16951
  const output = getOutput(cmd);
16828
16952
  const auth = resolveAuth4(opts, output);
16829
16953
  if (!auth)
16830
16954
  return;
16831
- const { client } = auth;
16955
+ const { client, projectId } = auth;
16832
16956
  const query = {};
16833
16957
  if (opts.pattern)
16834
16958
  query["pattern"] = opts.pattern;
16835
16959
  if (opts.limit)
16836
16960
  query["limit"] = opts.limit;
16837
16961
  try {
16838
- const keys = await client.get(`/api/v1/projects/${opts.project}/kv`, query);
16962
+ const keys = await client.get(`/api/v1/projects/${projectId}/kv`, query);
16839
16963
  if (output.isJson()) {
16840
16964
  output.success(keys);
16841
16965
  return;
@@ -16853,14 +16977,14 @@ function registerKvCommand(program2) {
16853
16977
  }
16854
16978
  }
16855
16979
  });
16856
- kv.command("get <key>").description("Get the value for a key").requiredOption("--project <id>", "Project ID").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (key, opts, cmd) => {
16980
+ kv.command("get <key>").description("Get the value for a key").option("--project <id>", "Project ID (defaults to dura.json)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (key, opts, cmd) => {
16857
16981
  const output = getOutput(cmd);
16858
16982
  const auth = resolveAuth4(opts, output);
16859
16983
  if (!auth)
16860
16984
  return;
16861
- const { client } = auth;
16985
+ const { client, projectId } = auth;
16862
16986
  try {
16863
- const result = await client.get(`/api/v1/projects/${opts.project}/kv/${encodeURIComponent(key)}`);
16987
+ const result = await client.get(`/api/v1/projects/${projectId}/kv/${encodeURIComponent(key)}`);
16864
16988
  output.success(result);
16865
16989
  } catch (err) {
16866
16990
  if (err instanceof ApiClientError) {
@@ -16870,12 +16994,12 @@ function registerKvCommand(program2) {
16870
16994
  }
16871
16995
  }
16872
16996
  });
16873
- kv.command("set <key> <value>").description("Set a value for a key").requiredOption("--project <id>", "Project ID").option("--ttl <seconds>", "Time-to-live in seconds (0 = no expiry)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (key, valueStr, opts, cmd) => {
16997
+ kv.command("set <key> <value>").description("Set a value for a key").option("--project <id>", "Project ID (defaults to dura.json)").option("--ttl <seconds>", "Time-to-live in seconds (0 = no expiry)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (key, valueStr, opts, cmd) => {
16874
16998
  const output = getOutput(cmd);
16875
16999
  const auth = resolveAuth4(opts, output);
16876
17000
  if (!auth)
16877
17001
  return;
16878
- const { client } = auth;
17002
+ const { client, projectId } = auth;
16879
17003
  let value;
16880
17004
  try {
16881
17005
  value = JSON.parse(valueStr);
@@ -16887,7 +17011,7 @@ function registerKvCommand(program2) {
16887
17011
  body.ttl = parseInt(opts.ttl, 10);
16888
17012
  }
16889
17013
  try {
16890
- const result = await client.put(`/api/v1/projects/${opts.project}/kv/${encodeURIComponent(key)}`, body);
17014
+ const result = await client.put(`/api/v1/projects/${projectId}/kv/${encodeURIComponent(key)}`, body);
16891
17015
  output.success(result);
16892
17016
  } catch (err) {
16893
17017
  if (err instanceof ApiClientError) {
@@ -16897,7 +17021,7 @@ function registerKvCommand(program2) {
16897
17021
  }
16898
17022
  }
16899
17023
  });
16900
- kv.command("delete <key>").description("Delete a key from the KV namespace").requiredOption("--project <id>", "Project ID").option("--confirm", "Required to actually perform the delete").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (key, opts, cmd) => {
17024
+ kv.command("delete <key>").description("Delete a key from the KV namespace").option("--project <id>", "Project ID (defaults to dura.json)").option("--confirm", "Required to actually perform the delete").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (key, opts, cmd) => {
16901
17025
  const output = getOutput(cmd);
16902
17026
  if (!opts.confirm) {
16903
17027
  output.error("KV_DELETE_CONFIRM_REQUIRED", `This will permanently delete the key "${key}".`, "Re-run with --confirm to proceed.");
@@ -16906,9 +17030,9 @@ function registerKvCommand(program2) {
16906
17030
  const auth = resolveAuth4(opts, output);
16907
17031
  if (!auth)
16908
17032
  return;
16909
- const { client } = auth;
17033
+ const { client, projectId } = auth;
16910
17034
  try {
16911
- await client.delete(`/api/v1/projects/${opts.project}/kv/${encodeURIComponent(key)}`);
17035
+ await client.delete(`/api/v1/projects/${projectId}/kv/${encodeURIComponent(key)}`);
16912
17036
  output.success({ deleted: true, key });
16913
17037
  } catch (err) {
16914
17038
  if (err instanceof ApiClientError) {
@@ -16918,7 +17042,7 @@ function registerKvCommand(program2) {
16918
17042
  }
16919
17043
  }
16920
17044
  });
16921
- kv.command("flush").description("Remove all keys in the project KV namespace").requiredOption("--project <id>", "Project ID").option("--confirm", "Required to actually perform the flush").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
17045
+ kv.command("flush").description("Remove all keys in the project KV namespace").option("--project <id>", "Project ID (defaults to dura.json)").option("--confirm", "Required to actually perform the flush").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
16922
17046
  const output = getOutput(cmd);
16923
17047
  if (!opts.confirm) {
16924
17048
  output.error("KV_FLUSH_CONFIRM_REQUIRED", "This will permanently delete ALL keys for the project.", "Re-run with --confirm to proceed.");
@@ -16927,9 +17051,9 @@ function registerKvCommand(program2) {
16927
17051
  const auth = resolveAuth4(opts, output);
16928
17052
  if (!auth)
16929
17053
  return;
16930
- const { client } = auth;
17054
+ const { client, projectId } = auth;
16931
17055
  try {
16932
- await client.delete(`/api/v1/projects/${opts.project}/kv`);
17056
+ await client.delete(`/api/v1/projects/${projectId}/kv`);
16933
17057
  output.success({ flushed: true });
16934
17058
  } catch (err) {
16935
17059
  if (err instanceof ApiClientError) {
@@ -16939,14 +17063,14 @@ function registerKvCommand(program2) {
16939
17063
  }
16940
17064
  }
16941
17065
  });
16942
- kv.command("stats").description("Show KV usage statistics for the project").requiredOption("--project <id>", "Project ID").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
17066
+ kv.command("stats").description("Show KV usage statistics for the project").option("--project <id>", "Project ID (defaults to dura.json)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
16943
17067
  const output = getOutput(cmd);
16944
17068
  const auth = resolveAuth4(opts, output);
16945
17069
  if (!auth)
16946
17070
  return;
16947
- const { client } = auth;
17071
+ const { client, projectId } = auth;
16948
17072
  try {
16949
- const stats = await client.get(`/api/v1/projects/${opts.project}/kv/stats`);
17073
+ const stats = await client.get(`/api/v1/projects/${projectId}/kv/stats`);
16950
17074
  output.success(stats);
16951
17075
  } catch (err) {
16952
17076
  if (err instanceof ApiClientError) {
@@ -16961,6 +17085,7 @@ var init_kv2 = __esm(() => {
16961
17085
  init_src3();
16962
17086
  init_api_client();
16963
17087
  init_config_store();
17088
+ init_project_id();
16964
17089
  });
16965
17090
 
16966
17091
  // src/commands/webhook.ts
@@ -17001,10 +17126,10 @@ function registerWebhookCommand(program2) {
17001
17126
  return;
17002
17127
  }
17003
17128
  const events = opts.events ? opts.events.split(",").map((e) => e.trim()) : undefined;
17004
- let transform;
17129
+ let transform2;
17005
17130
  if (opts.transform) {
17006
17131
  try {
17007
- transform = JSON.parse(opts.transform);
17132
+ transform2 = JSON.parse(opts.transform);
17008
17133
  } catch {
17009
17134
  output.error("VALIDATION_ERROR", "--transform must be valid JSON");
17010
17135
  return;
@@ -17015,7 +17140,7 @@ function registerWebhookCommand(program2) {
17015
17140
  slug: opts.slug ?? opts.name.toLowerCase().replace(/[^a-z0-9]+/g, "-"),
17016
17141
  source: { type: opts.source, events },
17017
17142
  destinations: [{ url: opts.forward, method: "POST" }],
17018
- transform: transform ? { template: transform } : undefined
17143
+ transform: transform2 ? { template: transform2 } : undefined
17019
17144
  };
17020
17145
  try {
17021
17146
  const result = await client.post(webhookBase(opts.project), body);
@@ -17406,24 +17531,29 @@ __export(exports_deployments, {
17406
17531
  registerDeploymentsCommand: () => registerDeploymentsCommand
17407
17532
  });
17408
17533
  function resolveAuth6(opts, output) {
17534
+ const projectId = resolveProjectId({ project: opts.project });
17535
+ if (!projectId) {
17536
+ output.error("PROJECT_REQUIRED", "No project ID. Use --project or run this command inside a dura.json project.");
17537
+ return null;
17538
+ }
17409
17539
  const token = opts.token || getAuthToken();
17410
17540
  if (!token) {
17411
17541
  output.error("AUTH_REQUIRED", "Not logged in", "Run: dura login");
17412
17542
  return null;
17413
17543
  }
17414
17544
  const apiUrl = opts.apiUrl || getApiUrl();
17415
- return { client: new ApiClient(apiUrl, token) };
17545
+ return { client: new ApiClient(apiUrl, token), projectId };
17416
17546
  }
17417
17547
  function registerDeploymentsCommand(program2) {
17418
17548
  const deployments2 = program2.command("deployments").description("View deployment history for a project");
17419
- deployments2.command("list").description("List deployments for a project").requiredOption("--project <id>", "Project ID").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
17549
+ deployments2.command("list").description("List deployments for a project").option("--project <id>", "Project ID (defaults to dura.json)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
17420
17550
  const output = getOutput(cmd);
17421
17551
  const auth = resolveAuth6(opts, output);
17422
17552
  if (!auth)
17423
17553
  return;
17424
- const { client } = auth;
17554
+ const { client, projectId } = auth;
17425
17555
  try {
17426
- const deploymentList = await client.get(`/api/v1/projects/${opts.project}/deployments`);
17556
+ const deploymentList = await client.get(`/api/v1/projects/${projectId}/deployments`);
17427
17557
  if (output.isJson()) {
17428
17558
  output.success(deploymentList);
17429
17559
  return;
@@ -17441,14 +17571,14 @@ function registerDeploymentsCommand(program2) {
17441
17571
  }
17442
17572
  }
17443
17573
  });
17444
- deployments2.command("get").description("Show details for a specific deployment").requiredOption("--project <id>", "Project ID").requiredOption("--id <deploymentId>", "Deployment ID").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
17574
+ deployments2.command("get").description("Show details for a specific deployment").option("--project <id>", "Project ID (defaults to dura.json)").requiredOption("--id <deploymentId>", "Deployment ID").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
17445
17575
  const output = getOutput(cmd);
17446
17576
  const auth = resolveAuth6(opts, output);
17447
17577
  if (!auth)
17448
17578
  return;
17449
- const { client } = auth;
17579
+ const { client, projectId } = auth;
17450
17580
  try {
17451
- const deployment = await client.get(`/api/v1/projects/${opts.project}/deployments/${opts.id}`);
17581
+ const deployment = await client.get(`/api/v1/projects/${projectId}/deployments/${opts.id}`);
17452
17582
  output.success(deployment);
17453
17583
  } catch (err) {
17454
17584
  if (err instanceof ApiClientError) {
@@ -17463,6 +17593,7 @@ var init_deployments2 = __esm(() => {
17463
17593
  init_src3();
17464
17594
  init_api_client();
17465
17595
  init_config_store();
17596
+ init_project_id();
17466
17597
  });
17467
17598
 
17468
17599
  // src/commands/diagnose.ts
@@ -17529,8 +17660,13 @@ Prevention: ${preventionAdvice}`);
17529
17660
  `);
17530
17661
  }
17531
17662
  function registerDiagnoseCommand(program2) {
17532
- program2.command("diagnose [execution-id]").description("AI-powered diagnosis of a failed execution — identify root cause and suggested fix").requiredOption("--project <id>", "Project ID").option("--latest", "Diagnose the most recent failed execution").option("--lookback <duration>", "Lookback window for related executions (e.g. 24h, 48h, 7d)").option("--force", "Force regeneration even if a cached diagnosis exists").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (executionId, opts, cmd) => {
17663
+ program2.command("diagnose [execution-id]").description("AI-powered diagnosis of a failed execution — identify root cause and suggested fix").option("--project <id>", "Project ID (defaults to dura.json)").option("--latest", "Diagnose the most recent failed execution").option("--lookback <duration>", "Lookback window for related executions (e.g. 24h, 48h, 7d)").option("--force", "Force regeneration even if a cached diagnosis exists").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (executionId, opts, cmd) => {
17533
17664
  const output = getOutput(cmd);
17665
+ const projectId = resolveProjectId({ project: opts.project });
17666
+ if (!projectId) {
17667
+ output.error("PROJECT_REQUIRED", "No project ID. Use --project or run this command inside a dura.json project.");
17668
+ return;
17669
+ }
17534
17670
  const token = opts.token || getAuthToken();
17535
17671
  if (!token) {
17536
17672
  output.error("AUTH_REQUIRED", "Not logged in", "Run: dura login");
@@ -17545,7 +17681,7 @@ function registerDiagnoseCommand(program2) {
17545
17681
  try {
17546
17682
  let targetExecutionId = executionId;
17547
17683
  if (opts.latest) {
17548
- const executions = await client.get(`/api/v1/projects/${opts.project}/executions?status=failed&limit=1`);
17684
+ const executions = await client.get(`/api/v1/projects/${projectId}/executions?status=failed&limit=1`);
17549
17685
  if (!executions || executions.length === 0) {
17550
17686
  output.error("NO_FAILED_EXECUTIONS", "No failed executions found for this project");
17551
17687
  return;
@@ -17557,7 +17693,7 @@ function registerDiagnoseCommand(program2) {
17557
17693
  Analyzing execution ${targetExecutionId}...`);
17558
17694
  }
17559
17695
  const lookbackHours = parseLookbackHours(opts.lookback);
17560
- const diagnosis = await client.post(`/api/v1/projects/${opts.project}/executions/${targetExecutionId}/diagnose`, {
17696
+ const diagnosis = await client.post(`/api/v1/projects/${projectId}/executions/${targetExecutionId}/diagnose`, {
17561
17697
  lookbackHours,
17562
17698
  force: opts.force ?? false
17563
17699
  });
@@ -17579,6 +17715,7 @@ var init_diagnose = __esm(() => {
17579
17715
  init_src3();
17580
17716
  init_api_client();
17581
17717
  init_config_store();
17718
+ init_project_id();
17582
17719
  });
17583
17720
 
17584
17721
  // src/commands/create.ts
@@ -17588,27 +17725,27 @@ __export(exports_create, {
17588
17725
  registerAddCommand: () => registerAddCommand
17589
17726
  });
17590
17727
  import {
17591
- existsSync as existsSync9,
17728
+ existsSync as existsSync10,
17592
17729
  mkdirSync as mkdirSync5,
17593
17730
  writeFileSync as writeFileSync8,
17594
- readFileSync as readFileSync8,
17731
+ readFileSync as readFileSync9,
17595
17732
  readdirSync as readdirSync4
17596
17733
  } from "node:fs";
17597
- import { join as join10, resolve as resolve5, dirname } from "node:path";
17734
+ import { join as join11, resolve as resolve5, dirname } from "node:path";
17598
17735
  function slugifyDescription(description) {
17599
17736
  return description.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 30).replace(/-+$/, "");
17600
17737
  }
17601
17738
  function getExistingRoutes(projectDir) {
17602
- const routesDir = join10(projectDir, "routes");
17603
- const jobsDir = join10(projectDir, "jobs");
17739
+ const routesDir = join11(projectDir, "routes");
17740
+ const jobsDir = join11(projectDir, "jobs");
17604
17741
  const routes = [];
17605
- if (existsSync9(routesDir)) {
17742
+ if (existsSync10(routesDir)) {
17606
17743
  try {
17607
17744
  const files = readdirSync4(routesDir);
17608
17745
  routes.push(...files.map((f) => `routes/${f}`));
17609
17746
  } catch {}
17610
17747
  }
17611
- if (existsSync9(jobsDir)) {
17748
+ if (existsSync10(jobsDir)) {
17612
17749
  try {
17613
17750
  const files = readdirSync4(jobsDir);
17614
17751
  routes.push(...files.map((f) => `jobs/${f}`));
@@ -17618,9 +17755,9 @@ function getExistingRoutes(projectDir) {
17618
17755
  }
17619
17756
  function writeGeneratedFiles(projectDir, files) {
17620
17757
  for (const file of files) {
17621
- const filePath = join10(projectDir, file.path);
17758
+ const filePath = join11(projectDir, file.path);
17622
17759
  const dir = dirname(filePath);
17623
- if (!existsSync9(dir)) {
17760
+ if (!existsSync10(dir)) {
17624
17761
  mkdirSync5(dir, { recursive: true });
17625
17762
  }
17626
17763
  writeFileSync8(filePath, file.content, "utf-8");
@@ -17652,8 +17789,8 @@ function registerCreateCommand(program2) {
17652
17789
  }
17653
17790
  const projectName = opts.name || slugifyDescription(description);
17654
17791
  const parentDir = opts.dir ? resolve5(opts.dir) : process.cwd();
17655
- const projectDir = join10(parentDir, projectName);
17656
- if (existsSync9(projectDir)) {
17792
+ const projectDir = join11(parentDir, projectName);
17793
+ if (existsSync10(projectDir)) {
17657
17794
  output.error("DIRECTORY_EXISTS", `Directory "${projectName}" already exists`, "Choose a different name with --name or remove the existing directory");
17658
17795
  return;
17659
17796
  }
@@ -17682,8 +17819,8 @@ function registerCreateCommand(program2) {
17682
17819
  return;
17683
17820
  }
17684
17821
  }
17685
- mkdirSync5(join10(projectDir, "routes"), { recursive: true });
17686
- mkdirSync5(join10(projectDir, "jobs"), { recursive: true });
17822
+ mkdirSync5(join11(projectDir, "routes"), { recursive: true });
17823
+ mkdirSync5(join11(projectDir, "jobs"), { recursive: true });
17687
17824
  writeGeneratedFiles(projectDir, generateResult.files);
17688
17825
  output.info(`Files written to ${projectDir}`);
17689
17826
  if (opts.deploy !== false) {
@@ -17714,15 +17851,15 @@ function registerAddCommand(program2) {
17714
17851
  return;
17715
17852
  }
17716
17853
  const projectDir = opts.dir ? resolve5(opts.dir) : process.cwd();
17717
- const duraJsonPath = join10(projectDir, "dura.json");
17718
- if (!existsSync9(duraJsonPath)) {
17719
- output.error("NOT_A_DURA_PROJECT", "No dura.json found in the current directory", "Run this command from a Dura project directory or use --dir");
17854
+ const duraJsonPath = join11(projectDir, "dura.json");
17855
+ if (!existsSync10(duraJsonPath)) {
17856
+ output.error("NOT_A_DURA_PROJECT", "No dura.json found in the current directory", "Run this command from a dura project directory or use --dir");
17720
17857
  return;
17721
17858
  }
17722
17859
  const existingRoutes = getExistingRoutes(projectDir);
17723
17860
  let projectConfig = {};
17724
17861
  try {
17725
- projectConfig = JSON.parse(readFileSync8(duraJsonPath, "utf-8"));
17862
+ projectConfig = JSON.parse(readFileSync9(duraJsonPath, "utf-8"));
17726
17863
  } catch {}
17727
17864
  const apiUrl = getApiUrl();
17728
17865
  const client = new ApiClient(apiUrl, token);
@@ -17994,23 +18131,29 @@ __export(exports_env, {
17994
18131
  registerCanaryCommand: () => registerCanaryCommand
17995
18132
  });
17996
18133
  function resolveClient(opts, output) {
18134
+ const projectId = resolveProjectId({ project: opts.project });
18135
+ if (!projectId) {
18136
+ output.error("PROJECT_REQUIRED", "No project ID. Use --project or run this command inside a dura.json project.");
18137
+ return null;
18138
+ }
17997
18139
  const token = opts.token || getAuthToken();
17998
18140
  if (!token) {
17999
18141
  output.error("AUTH_REQUIRED", "Not logged in", "Run: dura login");
18000
18142
  return null;
18001
18143
  }
18002
18144
  const apiUrl = opts.apiUrl || getApiUrl();
18003
- return new ApiClient(apiUrl, token);
18145
+ return { client: new ApiClient(apiUrl, token), projectId };
18004
18146
  }
18005
18147
  function registerEnvCommand(program2) {
18006
18148
  const env = program2.command("env").description("Manage project environments");
18007
- env.command("create <name>").description("Create a new environment").requiredOption("--project <id>", "Project ID").option("--type <type>", "Environment type: production, staging, preview, custom", "custom").option("--slug <slug>", "URL slug (defaults to name)").option("--inherit-secrets <env>", "Inherit secrets from this environment ID").option("--auto-deploy", "Enable auto-deploy").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (name, opts, cmd) => {
18149
+ env.command("create <name>").description("Create a new environment").option("--project <id>", "Project ID (defaults to dura.json)").option("--type <type>", "Environment type: production, staging, preview, custom", "custom").option("--slug <slug>", "URL slug (defaults to name)").option("--inherit-secrets <env>", "Inherit secrets from this environment ID").option("--auto-deploy", "Enable auto-deploy").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (name, opts, cmd) => {
18008
18150
  const output = getOutput(cmd);
18009
- const client = resolveClient(opts, output);
18010
- if (!client)
18151
+ const resolved = resolveClient(opts, output);
18152
+ if (!resolved)
18011
18153
  return;
18154
+ const { client, projectId } = resolved;
18012
18155
  try {
18013
- const env2 = await client.post(`/api/v1/projects/${opts.project}/environments`, {
18156
+ const env2 = await client.post(`/api/v1/projects/${projectId}/environments`, {
18014
18157
  name,
18015
18158
  slug: opts.slug ?? name.toLowerCase().replace(/[^a-z0-9-]/g, "-"),
18016
18159
  type: opts.type,
@@ -18026,13 +18169,14 @@ function registerEnvCommand(program2) {
18026
18169
  }
18027
18170
  }
18028
18171
  });
18029
- env.command("list").description("List all environments for a project").requiredOption("--project <id>", "Project ID").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
18172
+ env.command("list").description("List all environments for a project").option("--project <id>", "Project ID (defaults to dura.json)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
18030
18173
  const output = getOutput(cmd);
18031
- const client = resolveClient(opts, output);
18032
- if (!client)
18174
+ const resolved = resolveClient(opts, output);
18175
+ if (!resolved)
18033
18176
  return;
18177
+ const { client, projectId } = resolved;
18034
18178
  try {
18035
- const envs = await client.get(`/api/v1/projects/${opts.project}/environments`);
18179
+ const envs = await client.get(`/api/v1/projects/${projectId}/environments`);
18036
18180
  output.success(envs);
18037
18181
  } catch (err) {
18038
18182
  if (err instanceof ApiClientError) {
@@ -18042,19 +18186,20 @@ function registerEnvCommand(program2) {
18042
18186
  }
18043
18187
  }
18044
18188
  });
18045
- env.command("delete <name>").description("Delete an environment").requiredOption("--project <id>", "Project ID").option("--env-id <id>", "Environment ID (alternative to name)").option("--confirm", "Confirm deletion (required for destructive operation)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (name, opts, cmd) => {
18189
+ env.command("delete <name>").description("Delete an environment").option("--project <id>", "Project ID (defaults to dura.json)").option("--env-id <id>", "Environment ID (alternative to name)").option("--confirm", "Confirm deletion (required for destructive operation)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (name, opts, cmd) => {
18046
18190
  const output = getOutput(cmd);
18047
18191
  if (!opts.confirm) {
18048
18192
  output.error("CONFIRM_REQUIRED", `Pass --confirm to delete environment "${name}"`, "This action is irreversible");
18049
18193
  return;
18050
18194
  }
18051
- const client = resolveClient(opts, output);
18052
- if (!client)
18195
+ const resolved = resolveClient(opts, output);
18196
+ if (!resolved)
18053
18197
  return;
18198
+ const { client, projectId } = resolved;
18054
18199
  try {
18055
18200
  let envId = opts.envId;
18056
18201
  if (!envId) {
18057
- const envs = await client.get(`/api/v1/projects/${opts.project}/environments`);
18202
+ const envs = await client.get(`/api/v1/projects/${projectId}/environments`);
18058
18203
  const found = envs.find((e) => e.name === name);
18059
18204
  if (!found) {
18060
18205
  output.error("ENV_NOT_FOUND", `Environment "${name}" not found`);
@@ -18062,7 +18207,7 @@ function registerEnvCommand(program2) {
18062
18207
  }
18063
18208
  envId = found.id;
18064
18209
  }
18065
- await client.delete(`/api/v1/projects/${opts.project}/environments/${envId}`);
18210
+ await client.delete(`/api/v1/projects/${projectId}/environments/${envId}`);
18066
18211
  output.success({ deleted: true, name });
18067
18212
  } catch (err) {
18068
18213
  if (err instanceof ApiClientError) {
@@ -18072,13 +18217,14 @@ function registerEnvCommand(program2) {
18072
18217
  }
18073
18218
  }
18074
18219
  });
18075
- env.command("diff <env1> <env2>").description("Compare two environments").requiredOption("--project <id>", "Project ID").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (env1, env2, opts, cmd) => {
18220
+ env.command("diff <env1> <env2>").description("Compare two environments").option("--project <id>", "Project ID (defaults to dura.json)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (env1, env2, opts, cmd) => {
18076
18221
  const output = getOutput(cmd);
18077
- const client = resolveClient(opts, output);
18078
- if (!client)
18222
+ const resolved = resolveClient(opts, output);
18223
+ if (!resolved)
18079
18224
  return;
18225
+ const { client, projectId } = resolved;
18080
18226
  try {
18081
- const envs = await client.get(`/api/v1/projects/${opts.project}/environments`);
18227
+ const envs = await client.get(`/api/v1/projects/${projectId}/environments`);
18082
18228
  const e1 = envs.find((e) => e.name === env1 || e.slug === env1);
18083
18229
  const e2 = envs.find((e) => e.name === env2 || e.slug === env2);
18084
18230
  if (!e1) {
@@ -18105,8 +18251,13 @@ function registerEnvCommand(program2) {
18105
18251
  });
18106
18252
  }
18107
18253
  function registerPromoteCommand(program2) {
18108
- program2.command("promote <source> <target>").description("Promote a deployment from source to target environment").requiredOption("--project <id>", "Project ID").option("--canary", "Use canary rollout (gradual traffic shift)").option("--steps <weights>", "Canary weight steps, comma-separated (e.g. 10,25,50,100)", "10,25,50,100").option("--interval <duration>", "Time between canary steps (e.g. 5m)", "5m").option("--deployment-id <id>", "Specific deployment ID to promote (defaults to source active)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (source, target, opts, cmd) => {
18254
+ program2.command("promote <source> <target>").description("Promote a deployment from source to target environment").option("--project <id>", "Project ID (defaults to dura.json)").option("--canary", "Use canary rollout (gradual traffic shift)").option("--steps <weights>", "Canary weight steps, comma-separated (e.g. 10,25,50,100)", "10,25,50,100").option("--interval <duration>", "Time between canary steps (e.g. 5m)", "5m").option("--deployment-id <id>", "Specific deployment ID to promote (defaults to source active)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (source, target, opts, cmd) => {
18109
18255
  const output = getOutput(cmd);
18256
+ const projectId = resolveProjectId({ project: opts.project });
18257
+ if (!projectId) {
18258
+ output.error("PROJECT_REQUIRED", "No project ID. Use --project or run this command inside a dura.json project.");
18259
+ return;
18260
+ }
18110
18261
  const token = opts.token || getAuthToken();
18111
18262
  if (!token) {
18112
18263
  output.error("AUTH_REQUIRED", "Not logged in", "Run: dura login");
@@ -18115,7 +18266,7 @@ function registerPromoteCommand(program2) {
18115
18266
  const apiUrl = opts.apiUrl || getApiUrl();
18116
18267
  const client = new ApiClient(apiUrl, token);
18117
18268
  try {
18118
- const envs = await client.get(`/api/v1/projects/${opts.project}/environments`);
18269
+ const envs = await client.get(`/api/v1/projects/${projectId}/environments`);
18119
18270
  const sourceEnv = envs.find((e) => e.name === source || e.slug === source);
18120
18271
  const targetEnv = envs.find((e) => e.name === target || e.slug === target);
18121
18272
  if (!sourceEnv) {
@@ -18139,7 +18290,7 @@ function registerPromoteCommand(program2) {
18139
18290
  delayMs: intervalMs
18140
18291
  }));
18141
18292
  }
18142
- const promotion = await client.post(`/api/v1/projects/${opts.project}/promote`, {
18293
+ const promotion = await client.post(`/api/v1/projects/${projectId}/promote`, {
18143
18294
  sourceEnvId: sourceEnv.id,
18144
18295
  targetEnvId: targetEnv.id,
18145
18296
  deploymentId,
@@ -18158,8 +18309,13 @@ function registerPromoteCommand(program2) {
18158
18309
  }
18159
18310
  function registerCanaryCommand(program2) {
18160
18311
  const canary = program2.command("canary").description("Manage canary deployments");
18161
- canary.command("status").description("Show active canary deployment status").requiredOption("--project <id>", "Project ID").requiredOption("--env <id>", "Environment ID").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
18312
+ canary.command("status").description("Show active canary deployment status").option("--project <id>", "Project ID (defaults to dura.json)").requiredOption("--env <id>", "Environment ID").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
18162
18313
  const output = getOutput(cmd);
18314
+ const projectId = resolveProjectId({ project: opts.project });
18315
+ if (!projectId) {
18316
+ output.error("PROJECT_REQUIRED", "No project ID. Use --project or run this command inside a dura.json project.");
18317
+ return;
18318
+ }
18163
18319
  const token = opts.token || getAuthToken();
18164
18320
  if (!token) {
18165
18321
  output.error("AUTH_REQUIRED", "Not logged in", "Run: dura login");
@@ -18168,7 +18324,7 @@ function registerCanaryCommand(program2) {
18168
18324
  const apiUrl = opts.apiUrl || getApiUrl();
18169
18325
  const client = new ApiClient(apiUrl, token);
18170
18326
  try {
18171
- const canary2 = await client.get(`/api/v1/projects/${opts.project}/canary?envId=${opts.env}`);
18327
+ const canary2 = await client.get(`/api/v1/projects/${projectId}/canary?envId=${opts.env}`);
18172
18328
  output.success(canary2);
18173
18329
  } catch (err) {
18174
18330
  if (err instanceof ApiClientError) {
@@ -18178,8 +18334,13 @@ function registerCanaryCommand(program2) {
18178
18334
  }
18179
18335
  }
18180
18336
  });
18181
- canary.command("complete").description("Force-complete an active canary (100% traffic to new)").requiredOption("--project <id>", "Project ID").requiredOption("--canary-id <id>", "Canary deployment ID").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
18337
+ canary.command("complete").description("Force-complete an active canary (100% traffic to new)").option("--project <id>", "Project ID (defaults to dura.json)").requiredOption("--canary-id <id>", "Canary deployment ID").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
18182
18338
  const output = getOutput(cmd);
18339
+ const projectId = resolveProjectId({ project: opts.project });
18340
+ if (!projectId) {
18341
+ output.error("PROJECT_REQUIRED", "No project ID. Use --project or run this command inside a dura.json project.");
18342
+ return;
18343
+ }
18183
18344
  const token = opts.token || getAuthToken();
18184
18345
  if (!token) {
18185
18346
  output.error("AUTH_REQUIRED", "Not logged in", "Run: dura login");
@@ -18188,7 +18349,7 @@ function registerCanaryCommand(program2) {
18188
18349
  const apiUrl = opts.apiUrl || getApiUrl();
18189
18350
  const client = new ApiClient(apiUrl, token);
18190
18351
  try {
18191
- const canary2 = await client.post(`/api/v1/projects/${opts.project}/canary/complete`, { canaryId: opts.canaryId });
18352
+ const canary2 = await client.post(`/api/v1/projects/${projectId}/canary/complete`, { canaryId: opts.canaryId });
18192
18353
  output.success(canary2);
18193
18354
  } catch (err) {
18194
18355
  if (err instanceof ApiClientError) {
@@ -18198,12 +18359,17 @@ function registerCanaryCommand(program2) {
18198
18359
  }
18199
18360
  }
18200
18361
  });
18201
- canary.command("rollback").description("Force-rollback an active canary (restore old deployment)").requiredOption("--project <id>", "Project ID").requiredOption("--canary-id <id>", "Canary deployment ID").option("--reason <reason>", "Reason for rollback").option("--confirm", "Confirm rollback (required)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
18362
+ canary.command("rollback").description("Force-rollback an active canary (restore old deployment)").option("--project <id>", "Project ID (defaults to dura.json)").requiredOption("--canary-id <id>", "Canary deployment ID").option("--reason <reason>", "Reason for rollback").option("--confirm", "Confirm rollback (required)").option("--api-url <url>", "API base URL").option("--token <token>", "Auth token").action(async (opts, cmd) => {
18202
18363
  const output = getOutput(cmd);
18203
18364
  if (!opts.confirm) {
18204
18365
  output.error("CONFIRM_REQUIRED", "Pass --confirm to rollback the canary", "This will restore 100% traffic to the old deployment");
18205
18366
  return;
18206
18367
  }
18368
+ const projectId = resolveProjectId({ project: opts.project });
18369
+ if (!projectId) {
18370
+ output.error("PROJECT_REQUIRED", "No project ID. Use --project or run this command inside a dura.json project.");
18371
+ return;
18372
+ }
18207
18373
  const token = opts.token || getAuthToken();
18208
18374
  if (!token) {
18209
18375
  output.error("AUTH_REQUIRED", "Not logged in", "Run: dura login");
@@ -18212,7 +18378,7 @@ function registerCanaryCommand(program2) {
18212
18378
  const apiUrl = opts.apiUrl || getApiUrl();
18213
18379
  const client = new ApiClient(apiUrl, token);
18214
18380
  try {
18215
- const canary2 = await client.post(`/api/v1/projects/${opts.project}/canary/rollback`, { canaryId: opts.canaryId, reason: opts.reason });
18381
+ const canary2 = await client.post(`/api/v1/projects/${projectId}/canary/rollback`, { canaryId: opts.canaryId, reason: opts.reason });
18216
18382
  output.success(canary2);
18217
18383
  } catch (err) {
18218
18384
  if (err instanceof ApiClientError) {
@@ -18246,6 +18412,7 @@ var init_env = __esm(() => {
18246
18412
  init_src3();
18247
18413
  init_api_client();
18248
18414
  init_config_store();
18415
+ init_project_id();
18249
18416
  });
18250
18417
 
18251
18418
  // src/commands/reports.ts
@@ -18881,30 +19048,30 @@ var init_heal = __esm(() => {
18881
19048
  });
18882
19049
 
18883
19050
  // src/lib/skill-installer.ts
18884
- import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync9 } from "node:fs";
18885
- import { join as join11, dirname as dirname2 } from "node:path";
19051
+ import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync9 } from "node:fs";
19052
+ import { join as join12, dirname as dirname2 } from "node:path";
18886
19053
  import { homedir as homedir2 } from "node:os";
18887
19054
  import { fileURLToPath } from "node:url";
18888
19055
  function getSkillSourceDir() {
18889
19056
  const __filename2 = fileURLToPath(import.meta.url);
18890
- return join11(dirname2(dirname2(__filename2)), "skills");
19057
+ return join12(dirname2(dirname2(__filename2)), "skills");
18891
19058
  }
18892
19059
  function getGlobalSkillsDir() {
18893
- return join11(homedir2(), ".claude", "skills", "dura");
19060
+ return join12(homedir2(), ".claude", "skills", "dura");
18894
19061
  }
18895
19062
  function getLocalSkillsDir(projectDir) {
18896
- return join11(projectDir, ".claude", "skills", "dura");
19063
+ return join12(projectDir, ".claude", "skills", "dura");
18897
19064
  }
18898
19065
  function installSkills(targetDir) {
18899
19066
  const sourceDir = getSkillSourceDir();
18900
- if (!existsSync10(targetDir)) {
19067
+ if (!existsSync11(targetDir)) {
18901
19068
  mkdirSync6(targetDir, { recursive: true });
18902
19069
  }
18903
19070
  const installedFiles = [];
18904
19071
  for (const file of SKILL_FILES) {
18905
- const sourcePath = join11(sourceDir, file);
18906
- const targetPath = join11(targetDir, file);
18907
- const content = readFileSync9(sourcePath, "utf-8");
19072
+ const sourcePath = join12(sourceDir, file);
19073
+ const targetPath = join12(targetDir, file);
19074
+ const content = readFileSync10(sourcePath, "utf-8");
18908
19075
  writeFileSync9(targetPath, content, "utf-8");
18909
19076
  installedFiles.push(targetPath);
18910
19077
  }
@@ -18929,10 +19096,10 @@ var exports_init = {};
18929
19096
  __export(exports_init, {
18930
19097
  registerInitCommand: () => registerInitCommand
18931
19098
  });
18932
- import { existsSync as existsSync11, mkdirSync as mkdirSync7, writeFileSync as writeFileSync10 } from "node:fs";
18933
- import { join as join12, resolve as resolve6, basename } from "node:path";
19099
+ import { existsSync as existsSync12, mkdirSync as mkdirSync7, writeFileSync as writeFileSync10 } from "node:fs";
19100
+ import { join as join13, resolve as resolve6, basename } from "node:path";
18934
19101
  function registerInitCommand(program2) {
18935
- program2.command("init").description("Initialize a Dura project in the current directory and install AI agent skills").option("--dir <path>", "Project directory (defaults to current dir)").option("--name <name>", "Project name (defaults to directory name)").option("--global", "Install AI agent skills globally (~/.claude/skills/dura/)").option("--local", "Install AI agent skills locally (.claude/skills/dura/)").option("--skip-skills", "Skip AI agent skill installation").action(async (opts, cmd) => {
19102
+ program2.command("init").description("Initialize a dura project in the current directory and install AI agent skills").option("--dir <path>", "Project directory (defaults to current dir)").option("--name <name>", "Project name (defaults to directory name)").option("--global", "Install AI agent skills globally (~/.claude/skills/dura/)").option("--local", "Install AI agent skills locally (.claude/skills/dura/)").option("--skip-skills", "Skip AI agent skill installation").action(async (opts, cmd) => {
18936
19103
  const output = getOutput(cmd);
18937
19104
  const projectDir = resolve6(opts.dir ?? process.cwd());
18938
19105
  const projectName = opts.name ?? sanitizeName(basename(projectDir));
@@ -18940,24 +19107,30 @@ function registerInitCommand(program2) {
18940
19107
  output.error("INVALID_NAME", `Invalid project name "${projectName}"`, "Project name must start with a letter and contain only letters, numbers, hyphens, and underscores. Use --name to specify a valid name.");
18941
19108
  return;
18942
19109
  }
18943
- if (existsSync11(join12(projectDir, "dura.json"))) {
18944
- output.error("ALREADY_INITIALIZED", "This directory already contains a dura.json", "Remove dura.json to re-initialize, or use dura new <name> to create a new project");
18945
- return;
19110
+ mkdirSync7(join13(projectDir, "routes"), { recursive: true });
19111
+ mkdirSync7(join13(projectDir, "jobs"), { recursive: true });
19112
+ const manifestPath = join13(projectDir, "dura.json");
19113
+ const alreadyInitialized = existsSync12(manifestPath);
19114
+ if (!alreadyInitialized) {
19115
+ writeFileSync10(manifestPath, duraJsonTemplate(projectName));
19116
+ } else {
19117
+ output.info(`dura.json already exists in ${projectDir} — leaving it in place.`);
18946
19118
  }
18947
- mkdirSync7(join12(projectDir, "routes"), { recursive: true });
18948
- mkdirSync7(join12(projectDir, "jobs"), { recursive: true });
18949
- writeFileSync10(join12(projectDir, "dura.json"), duraJsonTemplate(projectName));
18950
- const helloPath = join12(projectDir, "routes", "hello.ts");
18951
- if (existsSync11(helloPath)) {
19119
+ const helloPath = join13(projectDir, "routes", "hello.ts");
19120
+ if (existsSync12(helloPath)) {
18952
19121
  output.warn("routes/hello.ts already exists — skipping");
18953
19122
  } else {
18954
19123
  writeFileSync10(helloPath, HELLO_TEMPLATE);
18955
19124
  }
18956
- const gitignorePath = join12(projectDir, ".gitignore");
18957
- if (!existsSync11(gitignorePath)) {
19125
+ const gitignorePath = join13(projectDir, ".gitignore");
19126
+ if (!existsSync12(gitignorePath)) {
18958
19127
  writeFileSync10(gitignorePath, GITIGNORE_CONTENT2);
18959
19128
  }
18960
- output.info(`Initialized Dura project "${projectName}"`);
19129
+ if (alreadyInitialized) {
19130
+ output.info(`Re-checked dura project "${projectName}" in ${projectDir}`);
19131
+ } else {
19132
+ output.info(`Initialized dura project "${projectName}"`);
19133
+ }
18961
19134
  if (!opts.skipSkills) {
18962
19135
  let skillScope;
18963
19136
  if (opts.global) {
@@ -18982,6 +19155,7 @@ function registerInitCommand(program2) {
18982
19155
  }
18983
19156
  output.success({
18984
19157
  initialized: true,
19158
+ alreadyInitialized,
18985
19159
  name: projectName,
18986
19160
  path: projectDir,
18987
19161
  files: ["dura.json", "routes/hello.ts", "jobs/", ".gitignore"]
@@ -18990,7 +19164,7 @@ function registerInitCommand(program2) {
18990
19164
  output.info("");
18991
19165
  output.info("Quickstart:");
18992
19166
  output.info(" dura dev # Start the local dev server");
18993
- output.info(" dura deploy # Deploy to the Dura cloud");
19167
+ output.info(" dura deploy # Deploy to the dura cloud");
18994
19168
  output.info(" dura run hello # Manually trigger an automation");
18995
19169
  }
18996
19170
  });
@@ -19027,6 +19201,7 @@ dist
19027
19201
  `;
19028
19202
  var init_init = __esm(() => {
19029
19203
  init_src3();
19204
+ init_hello();
19030
19205
  init_skill_installer();
19031
19206
  });
19032
19207
 
@@ -19111,7 +19286,7 @@ var init_projects2 = __esm(() => {
19111
19286
  import { realpathSync } from "node:fs";
19112
19287
  function createProgram() {
19113
19288
  const program2 = new Command;
19114
- program2.name("dura").description("Dura.run CLI — manage your automations from the command line").version(CLI_VERSION, "-v, --version").option("--json", "Output in JSON format").hook("preAction", (thisCommand) => {
19289
+ program2.name("dura").description("dura.run CLI — manage your automations from the command line").version(CLI_VERSION, "-v, --version").option("--json", "Output in JSON format").hook("preAction", (thisCommand) => {
19115
19290
  const opts = thisCommand.optsWithGlobals();
19116
19291
  const output = new Output({ json: opts.json });
19117
19292
  thisCommand.setOptionValue("_output", output);
@@ -19211,7 +19386,7 @@ async function registerAllCommands(program2) {
19211
19386
  registerOpenApiCommand2(program2);
19212
19387
  return program2;
19213
19388
  }
19214
- var CLI_VERSION = "0.1.5";
19389
+ var CLI_VERSION = "0.2.1";
19215
19390
  var init_src3 = __esm(() => {
19216
19391
  init_esm();
19217
19392
  if (import.meta.url === `file://${realpathSync(process.argv[1] ?? "").replace(/\\/g, "/")}`) {
@@ -19228,4 +19403,4 @@ export {
19228
19403
  CLI_VERSION
19229
19404
  };
19230
19405
 
19231
- //# debugId=CD666733E632C5CB64756E2164756E21
19406
+ //# debugId=25AB4373003CF5A164756E2164756E21