@_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.
- package/README.md +13 -8
- package/dist/cli.mjs +173 -41
- 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
|
|
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
|
|
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,
|
|
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
|
|
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 |
|
|
73
|
-
|
|
74
|
-
| Dart | `dart` |
|
|
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/) (
|
|
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
|
|
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
|
|
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
|
-
|
|
6058
|
-
|
|
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
|
-
}
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
6166
|
+
description: "Dart SDK",
|
|
6094
6167
|
resolve: async (ctx) => {
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
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.
|
|
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) => {
|