@_davideast/jules-env 0.0.2 → 0.1.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.
Files changed (3) hide show
  1. package/README.md +13 -8
  2. package/dist/cli.mjs +173 -41
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -10,7 +10,7 @@ That's it. The runtime is installed, environment variables are set, and your she
10
10
 
11
11
  ```
12
12
  ✔ brew install dart-sdk
13
- Wrote .jules/shellenv
13
+ Wrote ~/.jules/shellenv
14
14
  export PATH="/opt/homebrew/opt/dart-sdk/bin:$PATH"
15
15
  export DART_SDK="/opt/homebrew/opt/dart-sdk/libexec"
16
16
  ```
@@ -21,11 +21,15 @@ Wrote .jules/shellenv
21
21
 
22
22
  1. **Recipe** — A recipe describes how to install a runtime. It probes the system (e.g., `brew --prefix dart-sdk`) but never modifies it.
23
23
  2. **Plan** — The recipe produces an execution plan: shell commands to run, environment variables to set, and paths to prepend.
24
- 3. **Execute** — The plan runs. Install steps that are already satisfied (checked via an optional `checkCmd`) are skipped. State is persisted to `.jules/shellenv`.
24
+ 3. **Execute** — The plan runs. Install steps that are already satisfied (checked via an optional `checkCmd`) are skipped. State is persisted to `~/.jules/shellenv`.
25
+
26
+ ### Data recipes
27
+
28
+ Recipes can also be defined as JSON files. A data recipe is a static execution plan — no system probing, no dynamic resolution. This makes them easy to generate programmatically (e.g., by an LLM). Data recipes are validated against a Zod schema at load time. See `src/recipes/ollama.json` for an example.
25
29
 
26
30
  ## Shell environment
27
31
 
28
- After execution, `.jules/shellenv` contains the environment your runtime needs:
32
+ After execution, `~/.jules/shellenv` contains the environment your runtime needs:
29
33
 
30
34
  ```bash
31
35
  export PATH="/opt/homebrew/opt/dart-sdk/bin:$PATH"
@@ -35,7 +39,7 @@ export DART_SDK="/opt/homebrew/opt/dart-sdk/libexec"
35
39
  Source it to activate:
36
40
 
37
41
  ```bash
38
- source .jules/shellenv
42
+ source ~/.jules/shellenv
39
43
  ```
40
44
 
41
45
  The file is appended to on subsequent runs, so multiple runtimes compose cleanly.
@@ -69,9 +73,10 @@ jules-env use dart --dry-run
69
73
 
70
74
  ## Available recipes
71
75
 
72
- | Runtime | Recipe | Install method | Description |
73
- |---------|--------|---------------|-------------|
74
- | Dart | `dart` | Homebrew | Installs the Dart SDK via `brew install dart-sdk` |
76
+ | Runtime | Recipe | Type | Description |
77
+ |---------|--------|------|-------------|
78
+ | Dart | `dart` | Code | Installs the Dart SDK (Homebrew on macOS, apt on Linux) |
79
+ | Ollama | `ollama` | Data | Installs Ollama with EmbeddingGemma model |
75
80
 
76
81
  ## Installation
77
82
 
@@ -95,7 +100,7 @@ bun run build
95
100
  ### Prerequisites
96
101
 
97
102
  - [Bun](https://bun.sh/)
98
- - [Homebrew](https://brew.sh/) (required for the Dart recipe)
103
+ - [Homebrew](https://brew.sh/) (macOS only, used by Dart recipe)
99
104
 
100
105
  ### Run tests
101
106
 
package/dist/cli.mjs CHANGED
@@ -6011,11 +6011,24 @@ var ExecutionPlanSchema = z.object({
6011
6011
  content: z.string()
6012
6012
  })).default([])
6013
6013
  });
6014
+ var DataRecipeSchema = z.object({
6015
+ name: z.string(),
6016
+ description: z.string(),
6017
+ defaultPreset: z.string().optional(),
6018
+ installSteps: z.array(ShellStepSchema),
6019
+ env: z.record(z.string()).default({}),
6020
+ paths: z.array(z.string()).default([]),
6021
+ files: z.array(z.object({
6022
+ path: z.string(),
6023
+ content: z.string()
6024
+ })).default([])
6025
+ });
6014
6026
 
6015
6027
  // src/core/executor.ts
6016
6028
  import { spawn } from "node:child_process";
6017
6029
  import { mkdir, writeFile, appendFile } from "node:fs/promises";
6018
6030
  import { resolve, dirname } from "node:path";
6031
+ import { homedir } from "node:os";
6019
6032
  async function executePlan(plan, dryRun) {
6020
6033
  if (dryRun) {
6021
6034
  console.log("--- DRY RUN: Execution Plan ---");
@@ -6031,8 +6044,7 @@ async function executePlan(plan, dryRun) {
6031
6044
  console.log(`[${step.id}] ${step.label}...`);
6032
6045
  let skip = false;
6033
6046
  if (step.checkCmd) {
6034
- const checkParts = step.checkCmd.split(" ");
6035
- const check = spawn(checkParts[0], checkParts.slice(1), {
6047
+ const check = spawn("sh", ["-c", step.checkCmd], {
6036
6048
  stdio: "ignore"
6037
6049
  });
6038
6050
  const exitCode = await new Promise((res) => check.on("close", (code) => res(code ?? 1)));
@@ -6042,8 +6054,7 @@ async function executePlan(plan, dryRun) {
6042
6054
  }
6043
6055
  }
6044
6056
  if (!skip) {
6045
- const cmdParts = step.cmd.split(" ");
6046
- const proc = spawn(cmdParts[0], cmdParts.slice(1), {
6057
+ const proc = spawn("sh", ["-c", step.cmd], {
6047
6058
  stdio: "inherit"
6048
6059
  });
6049
6060
  const exitCode = await new Promise((res) => proc.on("close", (code) => res(code ?? 1)));
@@ -6054,16 +6065,18 @@ async function executePlan(plan, dryRun) {
6054
6065
  }
6055
6066
  }
6056
6067
  }
6057
- for (const file of plan.files) {
6058
- if (dryRun) {
6068
+ if (dryRun) {
6069
+ for (const file of plan.files) {
6059
6070
  console.log(`[File] Write to ${file.path}:`);
6060
6071
  console.log(file.content);
6061
- } else {
6072
+ }
6073
+ } else {
6074
+ await Promise.all(plan.files.map(async (file) => {
6062
6075
  await mkdir(dirname(file.path), { recursive: true });
6063
6076
  await writeFile(file.path, file.content);
6064
- }
6077
+ }));
6065
6078
  }
6066
- const julesDir = resolve(process.cwd(), ".jules");
6079
+ const julesDir = resolve(homedir(), ".jules");
6067
6080
  const stateFile = resolve(julesDir, "shellenv");
6068
6081
  let stateContent = "";
6069
6082
  if (plan.paths.length > 0) {
@@ -6075,56 +6088,174 @@ async function executePlan(plan, dryRun) {
6075
6088
  `;
6076
6089
  }
6077
6090
  if (dryRun) {
6078
- console.log(`[State] Append to .jules/shellenv:`);
6091
+ console.log(`[State] Append to ~/.jules/shellenv:`);
6079
6092
  console.log(stateContent);
6080
6093
  } else {
6081
6094
  if (stateContent) {
6082
6095
  await mkdir(julesDir, { recursive: true });
6083
6096
  await appendFile(stateFile, stateContent);
6084
- console.log(`Updated .jules/shellenv`);
6097
+ console.log(`Updated ~/.jules/shellenv`);
6085
6098
  }
6086
6099
  }
6087
6100
  }
6088
6101
 
6089
6102
  // src/recipes/dart.ts
6090
6103
  import { spawnSync } from "node:child_process";
6104
+ async function resolveDarwin() {
6105
+ const installSteps = [{
6106
+ id: "install-dart",
6107
+ label: "Install Dart SDK",
6108
+ cmd: "brew install dart-sdk",
6109
+ checkCmd: "brew list --versions dart-sdk"
6110
+ }];
6111
+ let dartPrefix = "";
6112
+ try {
6113
+ const result = spawnSync("brew", ["--prefix", "dart-sdk"], { encoding: "utf-8" });
6114
+ if (result.status === 0) {
6115
+ dartPrefix = result.stdout.trim();
6116
+ }
6117
+ } catch (e) {}
6118
+ if (!dartPrefix) {
6119
+ dartPrefix = "/usr/local/opt/dart-sdk";
6120
+ }
6121
+ const env = {
6122
+ DART_SDK: `${dartPrefix}/libexec`
6123
+ };
6124
+ const paths = [
6125
+ `${dartPrefix}/bin`
6126
+ ];
6127
+ return ExecutionPlanSchema.parse({ installSteps, env, paths });
6128
+ }
6129
+ async function resolveLinux() {
6130
+ const installSteps = [
6131
+ {
6132
+ id: "install-dart-prereqs",
6133
+ label: "Install prerequisites",
6134
+ cmd: "sudo apt-get update && sudo apt-get install -y apt-transport-https wget",
6135
+ checkCmd: "dpkg -s apt-transport-https && dpkg -s wget"
6136
+ },
6137
+ {
6138
+ id: "add-dart-signing-key",
6139
+ label: "Add Dart signing key",
6140
+ cmd: "wget -qO- https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo gpg --dearmor -o /usr/share/keyrings/dart.gpg",
6141
+ checkCmd: "test -f /usr/share/keyrings/dart.gpg"
6142
+ },
6143
+ {
6144
+ id: "add-dart-repo",
6145
+ label: "Add Dart repository",
6146
+ cmd: "echo 'deb [signed-by=/usr/share/keyrings/dart.gpg arch=amd64] https://storage.googleapis.com/dart-archive/channels/stable/release/latest/linux/debian stable main' | sudo tee /etc/apt/sources.list.d/dart_stable.list > /dev/null",
6147
+ checkCmd: "test -f /etc/apt/sources.list.d/dart_stable.list"
6148
+ },
6149
+ {
6150
+ id: "install-dart",
6151
+ label: "Install Dart SDK",
6152
+ cmd: "sudo apt-get update && sudo apt-get install -y dart",
6153
+ checkCmd: "dpkg -s dart"
6154
+ }
6155
+ ];
6156
+ const env = {
6157
+ DART_SDK: "/usr/lib/dart"
6158
+ };
6159
+ const paths = [
6160
+ "/usr/lib/dart/bin"
6161
+ ];
6162
+ return ExecutionPlanSchema.parse({ installSteps, env, paths });
6163
+ }
6091
6164
  var DartRecipe = {
6092
6165
  name: "dart",
6093
- description: "Dart SDK via Homebrew",
6166
+ description: "Dart SDK",
6094
6167
  resolve: async (ctx) => {
6095
- const installSteps = [{
6096
- id: "install-dart",
6097
- label: "Install Dart SDK",
6098
- cmd: "brew install dart-sdk",
6099
- checkCmd: "brew list --versions dart-sdk"
6100
- }];
6101
- let dartPrefix = "";
6102
- try {
6103
- const result = spawnSync("brew", ["--prefix", "dart-sdk"], { encoding: "utf-8" });
6104
- if (result.status === 0) {
6105
- dartPrefix = result.stdout.trim();
6106
- }
6107
- } catch (e) {}
6108
- if (!dartPrefix) {
6109
- dartPrefix = "/usr/local/opt/dart-sdk";
6110
- }
6111
- const env = {
6112
- DART_SDK: `${dartPrefix}/libexec`
6113
- };
6114
- const paths = [
6115
- `${dartPrefix}/bin`
6116
- ];
6117
- return ExecutionPlanSchema.parse({
6118
- installSteps,
6119
- env,
6120
- paths
6121
- });
6168
+ switch (process.platform) {
6169
+ case "darwin":
6170
+ return resolveDarwin();
6171
+ case "linux":
6172
+ return resolveLinux();
6173
+ default:
6174
+ throw new Error(`Unsupported platform: ${process.platform}`);
6175
+ }
6122
6176
  }
6123
6177
  };
6178
+
6179
+ // src/core/loader.ts
6180
+ function substituteVars(str, vars) {
6181
+ return str.replace(/\{\{(\w+)\}\}/g, (_, key) => {
6182
+ const val = vars[key];
6183
+ if (val === undefined) {
6184
+ throw new Error(`Missing required variable: {{${key}}}. Pass --preset to provide a value.`);
6185
+ }
6186
+ return val;
6187
+ });
6188
+ }
6189
+ function loadDataRecipe(data) {
6190
+ const parsed = DataRecipeSchema.parse(data);
6191
+ return {
6192
+ name: parsed.name,
6193
+ description: parsed.description,
6194
+ resolve: async (ctx) => {
6195
+ const vars = {};
6196
+ const preset = ctx.preset ?? parsed.defaultPreset;
6197
+ if (preset) {
6198
+ vars["preset"] = preset;
6199
+ }
6200
+ const installSteps = parsed.installSteps.map((step) => ({
6201
+ id: substituteVars(step.id, vars),
6202
+ label: substituteVars(step.label, vars),
6203
+ cmd: substituteVars(step.cmd, vars),
6204
+ ...step.checkCmd ? { checkCmd: substituteVars(step.checkCmd, vars) } : {}
6205
+ }));
6206
+ return {
6207
+ installSteps,
6208
+ env: parsed.env,
6209
+ paths: parsed.paths,
6210
+ files: parsed.files
6211
+ };
6212
+ }
6213
+ };
6214
+ }
6215
+ // src/recipes/ollama.json
6216
+ var ollama_default = {
6217
+ name: "ollama",
6218
+ description: "Ollama with configurable model",
6219
+ installSteps: [
6220
+ {
6221
+ id: "install-zstd",
6222
+ label: "Install zstd",
6223
+ cmd: "sudo apt-get update && sudo apt-get install -y zstd",
6224
+ checkCmd: "dpkg -s zstd"
6225
+ },
6226
+ {
6227
+ id: "install-ollama",
6228
+ label: "Install Ollama",
6229
+ cmd: "curl -fsSL https://ollama.com/install.sh | sh",
6230
+ checkCmd: "which ollama"
6231
+ },
6232
+ {
6233
+ id: "enable-ollama",
6234
+ label: "Enable Ollama service",
6235
+ cmd: "sudo systemctl daemon-reload && sudo systemctl enable --now ollama",
6236
+ checkCmd: "systemctl is-active ollama"
6237
+ },
6238
+ {
6239
+ id: "wait-for-ollama",
6240
+ label: "Wait for Ollama to initialize",
6241
+ cmd: "sleep 5"
6242
+ },
6243
+ {
6244
+ id: "pull-model",
6245
+ label: "Pull {{preset}} model",
6246
+ cmd: "ollama pull {{preset}}",
6247
+ checkCmd: "ollama list | grep {{preset}}"
6248
+ }
6249
+ ],
6250
+ env: {
6251
+ OLLAMA_HOST: "http://localhost:11434"
6252
+ },
6253
+ paths: []
6254
+ };
6124
6255
  // package.json
6125
6256
  var package_default = {
6126
6257
  name: "@_davideast/jules-env",
6127
- version: "0.0.2",
6258
+ version: "0.1.1",
6128
6259
  description: "Configure ephemeral development environments",
6129
6260
  license: "Apache-2.0",
6130
6261
  type: "module",
@@ -6164,7 +6295,8 @@ var package_default = {
6164
6295
  // src/cli.ts
6165
6296
  var program2 = new Command;
6166
6297
  var recipes = {
6167
- dart: DartRecipe
6298
+ dart: DartRecipe,
6299
+ ollama: loadDataRecipe(ollama_default)
6168
6300
  };
6169
6301
  program2.name("jules-env").description("Configure ephemeral development environments").version(package_default.version);
6170
6302
  program2.command("use <runtime>").description("Setup a runtime environment").option("--version <v>", "Version to install", "latest").option("--dry-run", "Simulate execution", false).option("--preset <p>", "Configuration preset").action(async (runtime, options) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@_davideast/jules-env",
3
- "version": "0.0.2",
3
+ "version": "0.1.1",
4
4
  "description": "Configure ephemeral development environments",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",