clawchef 0.1.4 → 0.1.5

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/README.md CHANGED
@@ -8,6 +8,7 @@ Recipe-driven OpenClaw environment orchestrator.
8
8
  - Accepts recipe input from local file/dir/archive and HTTP URL/archive.
9
9
  - Resolves `${var}` parameters from `--var`, environment, and defaults.
10
10
  - Auto-loads environment variables from `.env` in the current working directory.
11
+ - Supports loading env vars from a custom `.env` path/URL via `--env-file`.
11
12
  - Requires secrets to be injected via `--var` / `CLAWCHEF_VAR_*` (no inline secrets in recipe).
12
13
  - Prepares OpenClaw version (install or reuse).
13
14
  - When installed OpenClaw version mismatches recipe version, prompts: ignore / abort / force reinstall (silent mode auto-picks force reinstall).
@@ -39,6 +40,13 @@ Run recipe from URL:
39
40
  clawchef cook https://example.com/recipes/sample.yaml --provider remote
40
41
  ```
41
42
 
43
+ Run with custom env file (local path or HTTP URL):
44
+
45
+ ```bash
46
+ clawchef cook recipes/sample.yaml --env-file ./.env.staging
47
+ clawchef cook recipes/sample.yaml --env-file https://example.com/envs/staging.env
48
+ ```
49
+
42
50
  Run recipe from GitHub repository root (`recipe.yaml` at repo root):
43
51
 
44
52
  ```bash
@@ -190,6 +198,7 @@ await scaffold("./my-recipe-project", {
190
198
  - `plugins`: plugin npm specs to preinstall for this run (`string[]`)
191
199
  - `provider`: `command | remote | mock`
192
200
  - `remote`: remote provider config (same fields as CLI remote flags)
201
+ - `envFile`: custom env file path/URL; when set, default cwd `.env` loading is skipped
193
202
  - `dryRun`, `allowMissing`, `verbose`
194
203
  - `silent` (default: `true` in Node API)
195
204
  - `loadDotEnvFromCwd` (default: `true`)
@@ -212,6 +221,7 @@ Notes:
212
221
  If `params.<key>.required: true` and no value is found, run fails.
213
222
 
214
223
  If `.env` exists in the directory where `clawchef` is executed, it is loaded before recipe parsing.
224
+ If `--env-file` (or Node API `envFile`) is provided, only that env source is loaded.
215
225
 
216
226
  ## Recipe reference formats
217
227
 
package/dist/api.d.ts CHANGED
@@ -9,6 +9,7 @@ export interface CookOptions {
9
9
  silent?: boolean;
10
10
  provider?: OpenClawProvider;
11
11
  remote?: Partial<OpenClawRemoteConfig>;
12
+ envFile?: string;
12
13
  loadDotEnvFromCwd?: boolean;
13
14
  }
14
15
  export declare function cook(recipeRef: string, options?: CookOptions): Promise<void>;
package/dist/api.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import YAML from "js-yaml";
2
2
  import { ClawChefError } from "./errors.js";
3
- import { importDotEnvFromCwd } from "./env.js";
3
+ import { importDotEnvFromCwd, importDotEnvFromRef } from "./env.js";
4
4
  import { Logger } from "./logger.js";
5
5
  import { runRecipe } from "./orchestrator.js";
6
6
  import { loadRecipe, loadRecipeText } from "./recipe.js";
@@ -21,7 +21,10 @@ function normalizeCookOptions(options) {
21
21
  };
22
22
  }
23
23
  export async function cook(recipeRef, options = {}) {
24
- if (options.loadDotEnvFromCwd ?? true) {
24
+ if (options.envFile) {
25
+ await importDotEnvFromRef(options.envFile);
26
+ }
27
+ else if (options.loadDotEnvFromCwd ?? true) {
25
28
  importDotEnvFromCwd();
26
29
  }
27
30
  const runOptions = normalizeCookOptions(options);
package/dist/cli.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Command } from "commander";
2
2
  import { ClawChefError } from "./errors.js";
3
+ import { importDotEnvFromCwd, importDotEnvFromRef } from "./env.js";
3
4
  import { Logger } from "./logger.js";
4
5
  import { runRecipe } from "./orchestrator.js";
5
6
  import { loadRecipe, loadRecipeText } from "./recipe.js";
@@ -78,6 +79,7 @@ export function buildCli() {
78
79
  .option("--verbose", "Verbose logging", false)
79
80
  .option("-s, --silent", "Skip reset confirmation prompt", false)
80
81
  .option("--keep-openclaw-state", "Preserve existing OpenClaw state (skip factory reset)", false)
82
+ .option("--env-file <path-or-url>", "Load env vars from local file or HTTP URL")
81
83
  .option("--provider <provider>", "Execution provider: command | remote | mock")
82
84
  .option("--plugin <npm-spec>", "Preinstall plugin package (repeatable)", (v, p) => p.concat([v]), [])
83
85
  .option("--remote-base-url <url>", "Remote OpenClaw API base URL")
@@ -87,6 +89,12 @@ export function buildCli() {
87
89
  .option("--remote-timeout-ms <ms>", "Remote operation timeout in milliseconds")
88
90
  .option("--remote-operation-path <path>", "Remote operation endpoint path")
89
91
  .action(async (recipeRef, opts) => {
92
+ if (opts.envFile) {
93
+ await importDotEnvFromRef(String(opts.envFile));
94
+ }
95
+ else {
96
+ importDotEnvFromCwd();
97
+ }
90
98
  const provider = parseProvider(opts.provider ?? readEnv("CLAWCHEF_PROVIDER") ?? "command");
91
99
  const options = {
92
100
  vars: parseVarFlags(opts.var),
package/dist/env.d.ts CHANGED
@@ -1 +1,2 @@
1
1
  export declare function importDotEnvFromCwd(): void;
2
+ export declare function importDotEnvFromRef(ref: string): Promise<void>;
package/dist/env.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { existsSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
2
3
  import path from "node:path";
3
- import { config as loadDotenv } from "dotenv";
4
+ import { config as loadDotenv, parse as parseDotenv } from "dotenv";
4
5
  import { ClawChefError } from "./errors.js";
5
6
  export function importDotEnvFromCwd() {
6
7
  const envPath = path.resolve(process.cwd(), ".env");
@@ -12,3 +13,51 @@ export function importDotEnvFromCwd() {
12
13
  throw new ClawChefError(`Failed to load .env from current directory: ${result.error.message}`);
13
14
  }
14
15
  }
16
+ function isHttpUrl(value) {
17
+ try {
18
+ const url = new URL(value);
19
+ return url.protocol === "http:" || url.protocol === "https:";
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }
25
+ function applyEnv(entries) {
26
+ for (const [key, value] of Object.entries(entries)) {
27
+ if (process.env[key] === undefined) {
28
+ process.env[key] = value;
29
+ }
30
+ }
31
+ }
32
+ export async function importDotEnvFromRef(ref) {
33
+ const trimmed = ref.trim();
34
+ if (!trimmed) {
35
+ throw new ClawChefError("--env-file cannot be empty");
36
+ }
37
+ if (isHttpUrl(trimmed)) {
38
+ let response;
39
+ try {
40
+ response = await fetch(trimmed);
41
+ }
42
+ catch (err) {
43
+ const message = err instanceof Error ? err.message : String(err);
44
+ throw new ClawChefError(`Failed to fetch env file URL ${trimmed}: ${message}`);
45
+ }
46
+ if (!response.ok) {
47
+ throw new ClawChefError(`Failed to fetch env file URL ${trimmed}: HTTP ${response.status}`);
48
+ }
49
+ const content = await response.text();
50
+ applyEnv(parseDotenv(content));
51
+ return;
52
+ }
53
+ const envPath = path.resolve(trimmed);
54
+ let content;
55
+ try {
56
+ content = await readFile(envPath, "utf8");
57
+ }
58
+ catch (err) {
59
+ const message = err instanceof Error ? err.message : String(err);
60
+ throw new ClawChefError(`Failed to load env file ${envPath}: ${message}`);
61
+ }
62
+ applyEnv(parseDotenv(content));
63
+ }
package/dist/index.js CHANGED
@@ -1,9 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { buildCli } from "./cli.js";
3
- import { importDotEnvFromCwd } from "./env.js";
4
3
  import { ClawChefError } from "./errors.js";
5
4
  async function main() {
6
- importDotEnvFromCwd();
7
5
  const program = buildCli();
8
6
  await program.parseAsync(process.argv);
9
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawchef",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Recipe-driven OpenClaw environment orchestrator",
5
5
  "homepage": "https://renorzr.github.io/clawchef",
6
6
  "repository": {
package/src/api.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import YAML from "js-yaml";
2
2
  import { ClawChefError } from "./errors.js";
3
- import { importDotEnvFromCwd } from "./env.js";
3
+ import { importDotEnvFromCwd, importDotEnvFromRef } from "./env.js";
4
4
  import { Logger } from "./logger.js";
5
5
  import { runRecipe } from "./orchestrator.js";
6
6
  import { loadRecipe, loadRecipeText } from "./recipe.js";
@@ -18,6 +18,7 @@ export interface CookOptions {
18
18
  silent?: boolean;
19
19
  provider?: OpenClawProvider;
20
20
  remote?: Partial<OpenClawRemoteConfig>;
21
+ envFile?: string;
21
22
  loadDotEnvFromCwd?: boolean;
22
23
  }
23
24
 
@@ -37,7 +38,9 @@ function normalizeCookOptions(options: CookOptions): RunOptions {
37
38
  }
38
39
 
39
40
  export async function cook(recipeRef: string, options: CookOptions = {}): Promise<void> {
40
- if (options.loadDotEnvFromCwd ?? true) {
41
+ if (options.envFile) {
42
+ await importDotEnvFromRef(options.envFile);
43
+ } else if (options.loadDotEnvFromCwd ?? true) {
41
44
  importDotEnvFromCwd();
42
45
  }
43
46
 
package/src/cli.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Command } from "commander";
2
2
  import { ClawChefError } from "./errors.js";
3
+ import { importDotEnvFromCwd, importDotEnvFromRef } from "./env.js";
3
4
  import { Logger } from "./logger.js";
4
5
  import { runRecipe } from "./orchestrator.js";
5
6
  import { loadRecipe, loadRecipeText } from "./recipe.js";
@@ -88,6 +89,7 @@ export function buildCli(): Command {
88
89
  .option("--verbose", "Verbose logging", false)
89
90
  .option("-s, --silent", "Skip reset confirmation prompt", false)
90
91
  .option("--keep-openclaw-state", "Preserve existing OpenClaw state (skip factory reset)", false)
92
+ .option("--env-file <path-or-url>", "Load env vars from local file or HTTP URL")
91
93
  .option("--provider <provider>", "Execution provider: command | remote | mock")
92
94
  .option("--plugin <npm-spec>", "Preinstall plugin package (repeatable)", (v, p: string[]) => p.concat([v]), [])
93
95
  .option("--remote-base-url <url>", "Remote OpenClaw API base URL")
@@ -97,6 +99,12 @@ export function buildCli(): Command {
97
99
  .option("--remote-timeout-ms <ms>", "Remote operation timeout in milliseconds")
98
100
  .option("--remote-operation-path <path>", "Remote operation endpoint path")
99
101
  .action(async (recipeRef: string, opts) => {
102
+ if (opts.envFile) {
103
+ await importDotEnvFromRef(String(opts.envFile));
104
+ } else {
105
+ importDotEnvFromCwd();
106
+ }
107
+
100
108
  const provider = parseProvider(opts.provider ?? readEnv("CLAWCHEF_PROVIDER") ?? "command");
101
109
  const options: RunOptions = {
102
110
  vars: parseVarFlags(opts.var),
package/src/env.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { existsSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
2
3
  import path from "node:path";
3
- import { config as loadDotenv } from "dotenv";
4
+ import { config as loadDotenv, parse as parseDotenv } from "dotenv";
4
5
  import { ClawChefError } from "./errors.js";
5
6
 
6
7
  export function importDotEnvFromCwd(): void {
@@ -14,3 +15,53 @@ export function importDotEnvFromCwd(): void {
14
15
  throw new ClawChefError(`Failed to load .env from current directory: ${result.error.message}`);
15
16
  }
16
17
  }
18
+
19
+ function isHttpUrl(value: string): boolean {
20
+ try {
21
+ const url = new URL(value);
22
+ return url.protocol === "http:" || url.protocol === "https:";
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
27
+
28
+ function applyEnv(entries: Record<string, string>): void {
29
+ for (const [key, value] of Object.entries(entries)) {
30
+ if (process.env[key] === undefined) {
31
+ process.env[key] = value;
32
+ }
33
+ }
34
+ }
35
+
36
+ export async function importDotEnvFromRef(ref: string): Promise<void> {
37
+ const trimmed = ref.trim();
38
+ if (!trimmed) {
39
+ throw new ClawChefError("--env-file cannot be empty");
40
+ }
41
+
42
+ if (isHttpUrl(trimmed)) {
43
+ let response: Response;
44
+ try {
45
+ response = await fetch(trimmed);
46
+ } catch (err) {
47
+ const message = err instanceof Error ? err.message : String(err);
48
+ throw new ClawChefError(`Failed to fetch env file URL ${trimmed}: ${message}`);
49
+ }
50
+ if (!response.ok) {
51
+ throw new ClawChefError(`Failed to fetch env file URL ${trimmed}: HTTP ${response.status}`);
52
+ }
53
+ const content = await response.text();
54
+ applyEnv(parseDotenv(content));
55
+ return;
56
+ }
57
+
58
+ const envPath = path.resolve(trimmed);
59
+ let content: string;
60
+ try {
61
+ content = await readFile(envPath, "utf8");
62
+ } catch (err) {
63
+ const message = err instanceof Error ? err.message : String(err);
64
+ throw new ClawChefError(`Failed to load env file ${envPath}: ${message}`);
65
+ }
66
+ applyEnv(parseDotenv(content));
67
+ }
package/src/index.ts CHANGED
@@ -1,10 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { buildCli } from "./cli.js";
3
- import { importDotEnvFromCwd } from "./env.js";
4
3
  import { ClawChefError } from "./errors.js";
5
4
 
6
5
  async function main(): Promise<void> {
7
- importDotEnvFromCwd();
8
6
  const program = buildCli();
9
7
  await program.parseAsync(process.argv);
10
8
  }