moflo 4.8.71 → 4.8.73

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moflo",
3
- "version": "4.8.71",
3
+ "version": "4.8.73",
4
4
  "description": "MoFlo — AI agent orchestration for Claude Code. Forked from ruflo/claude-flow with patches applied to source, plus feature-level orchestration.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -112,7 +112,7 @@
112
112
  "@types/js-yaml": "^4.0.9",
113
113
  "@types/node": "^20.19.37",
114
114
  "eslint": "^8.0.0",
115
- "moflo": "^4.8.70",
115
+ "moflo": "^4.8.72",
116
116
  "tsx": "^4.21.0",
117
117
  "typescript": "^5.9.3",
118
118
  "vitest": "^4.0.0"
@@ -2,5 +2,5 @@
2
2
  * Auto-generated by build. Do not edit manually.
3
3
  * Source of truth: root package.json → scripts/sync-version.mjs
4
4
  */
5
- export const VERSION = '4.8.71';
5
+ export const VERSION = '4.8.73';
6
6
  //# sourceMappingURL=version.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moflo/cli",
3
- "version": "4.8.71",
3
+ "version": "4.8.73",
4
4
  "type": "module",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -15,12 +15,13 @@ import { execSync } from 'node:child_process';
15
15
  import { existsSync, readFileSync } from 'node:fs';
16
16
  import { platform } from 'node:os';
17
17
  import { join } from 'node:path';
18
+ import { escapeShellArg } from './shell.js';
18
19
  export const DEFAULT_SANDBOX_CONFIG = {
19
20
  enabled: false,
20
21
  tier: 'auto',
21
22
  };
22
23
  /** Recommended image for first-time Windows Docker sandbox setup. */
23
- export const RECOMMENDED_DOCKER_IMAGE = 'node:20-bookworm-slim';
24
+ export const RECOMMENDED_DOCKER_IMAGE = 'node:20-bookworm';
24
25
  // ============================================================================
25
26
  // Detection (cached)
26
27
  // ============================================================================
@@ -40,6 +41,7 @@ export function detectSandboxCapability() {
40
41
  /** Reset the cached result (for testing). */
41
42
  export function resetSandboxCache() {
42
43
  _cached = undefined;
44
+ _imageExistsCache.clear();
43
45
  }
44
46
  function detectUncached() {
45
47
  const os = platform();
@@ -165,32 +167,33 @@ export async function loadSandboxConfigFromProject(projectRoot) {
165
167
  * @throws Error if tier is 'full' but no OS sandbox is available.
166
168
  */
167
169
  export function resolveEffectiveSandbox(config, capability = detectSandboxCapability()) {
170
+ let resolved = config;
168
171
  // Config disabled or tier is denylist-only => no OS sandbox
169
- if (!config.enabled || config.tier === 'denylist-only') {
172
+ if (!resolved.enabled || resolved.tier === 'denylist-only') {
170
173
  return {
171
174
  useOsSandbox: false,
172
175
  capability,
173
- config,
176
+ config: resolved,
174
177
  displayStatus: `OS sandbox: disabled (denylist active)`,
175
178
  };
176
179
  }
177
- // Windows: give beginner-friendly setup instructions when sandboxing is
178
- // enabled but Docker isn't ready. Runs before the generic "not available"
179
- // branch so Windows users see actionable guidance instead of a terse
180
- // "not available (win32)" message.
180
+ // Windows: Docker is required for OS sandboxing. If Docker is available,
181
+ // auto-default the image and auto-pull it on first use so the user doesn't
182
+ // have to do manual setup. Only throw if Docker itself isn't installed/running.
181
183
  if (capability.platform === 'win32') {
182
184
  if (!capability.available) {
183
185
  throw new Error(formatWindowsDockerNotReadyMessage());
184
186
  }
185
- if (!config.dockerImage) {
186
- throw new Error(formatWindowsDockerImageMissingMessage());
187
+ const image = resolved.dockerImage || RECOMMENDED_DOCKER_IMAGE;
188
+ if (!resolved.dockerImage) {
189
+ resolved = { ...resolved, dockerImage: image };
187
190
  }
188
- if (!dockerImageExists(config.dockerImage)) {
189
- throw new Error(formatWindowsDockerImageNotPulledMessage(config.dockerImage));
191
+ if (!dockerImageExists(image)) {
192
+ dockerPullImage(image);
190
193
  }
191
194
  }
192
195
  // tier: full — require OS sandbox on non-Windows platforms
193
- if (config.tier === 'full' && !capability.available) {
196
+ if (resolved.tier === 'full' && !capability.available) {
194
197
  throw new Error(`Sandbox tier "full" requires an OS sandbox but none was detected on ${capability.platform}. ` +
195
198
  `Install bubblewrap (Linux) or set sandbox.tier to "auto".`);
196
199
  }
@@ -198,33 +201,51 @@ export function resolveEffectiveSandbox(config, capability = detectSandboxCapabi
198
201
  return {
199
202
  useOsSandbox: false,
200
203
  capability,
201
- config,
204
+ config: resolved,
202
205
  displayStatus: `OS sandbox: not available (${capability.platform})`,
203
206
  };
204
207
  }
205
208
  return {
206
209
  useOsSandbox: true,
207
210
  capability,
208
- config,
211
+ config: resolved,
209
212
  displayStatus: `OS sandbox: ${capability.tool} (${capability.platform})`,
210
213
  };
211
214
  }
215
+ const _imageExistsCache = new Map();
212
216
  /**
213
217
  * Check whether a Docker image is available locally (already pulled).
214
- * Returns false on any error (daemon down, image missing, docker not in PATH).
218
+ * Result is cached per image name for the process lifetime.
215
219
  */
216
220
  function dockerImageExists(image) {
221
+ const cached = _imageExistsCache.get(image);
222
+ if (cached !== undefined)
223
+ return cached;
217
224
  try {
218
- execSync(`docker image inspect ${shellQuote(image)}`, { stdio: 'ignore', timeout: 5000 });
225
+ execSync(`docker image inspect ${escapeShellArg(image)}`, { stdio: 'ignore', timeout: 5000 });
226
+ _imageExistsCache.set(image, true);
219
227
  return true;
220
228
  }
221
229
  catch {
222
230
  return false;
223
231
  }
224
232
  }
225
- /** Minimal shell quoting for image names — keeps the execSync call safe. */
226
- function shellQuote(value) {
227
- return `"${value.replace(/"/g, '\\"')}"`;
233
+ /**
234
+ * Pull a Docker image, printing a one-time setup banner so the user knows
235
+ * what's happening and why. Throws if the pull fails.
236
+ */
237
+ function dockerPullImage(image) {
238
+ console.log(`[spell] One-time setup: pulling Docker image ${image} for sandboxing...\n` +
239
+ ` This only happens once — Docker caches the image afterwards.`);
240
+ try {
241
+ execSync(`docker pull ${escapeShellArg(image)}`, { stdio: 'inherit', timeout: 300_000 });
242
+ _imageExistsCache.set(image, true);
243
+ console.log(`[spell] Docker image ${image} is ready.`);
244
+ }
245
+ catch {
246
+ throw new Error(`Failed to pull Docker image "${image}".\n\n` +
247
+ 'Make sure Docker Desktop is running and you have internet access, then try again.');
248
+ }
228
249
  }
229
250
  // ── Beginner-friendly setup messages (Windows) ──────────────────────────
230
251
  function formatWindowsDockerNotReadyMessage() {
@@ -241,51 +262,13 @@ function formatWindowsDockerNotReadyMessage() {
241
262
  ' Wait for the whale icon in your system tray to stop animating —',
242
263
  ' that means Docker is ready.',
243
264
  '',
244
- ' 3. Open PowerShell (or any terminal) and pull the recommended image:',
245
- ` docker pull ${RECOMMENDED_DOCKER_IMAGE}`,
246
- '',
247
- ' 4. Add this to your moflo.yaml:',
248
- ' sandbox:',
249
- ' enabled: true',
250
- ` dockerImage: ${RECOMMENDED_DOCKER_IMAGE}`,
265
+ `MoFlo will auto-pull the default image (${RECOMMENDED_DOCKER_IMAGE}) on`,
266
+ 'the first spell run.',
251
267
  '',
252
- 'Not ready to set this up? You can turn sandboxing off instead by setting',
268
+ 'Not ready to set this up? Turn sandboxing off by setting',
253
269
  '`sandbox.enabled: false` in moflo.yaml.',
254
270
  ].join('\n');
255
271
  }
256
- function formatWindowsDockerImageMissingMessage() {
257
- return [
258
- 'Sandboxing is enabled, but no Docker image is configured.',
259
- '',
260
- 'Docker is ready on this machine — it just needs to know which image to',
261
- 'run your spell steps inside. This is a one-time setup:',
262
- '',
263
- ' 1. Open PowerShell (or any terminal) and pull the recommended image:',
264
- ` docker pull ${RECOMMENDED_DOCKER_IMAGE}`,
265
- '',
266
- ' 2. Add this to your moflo.yaml:',
267
- ' sandbox:',
268
- ' enabled: true',
269
- ` dockerImage: ${RECOMMENDED_DOCKER_IMAGE}`,
270
- '',
271
- `The recommended image (${RECOMMENDED_DOCKER_IMAGE}) includes node, npm,`,
272
- 'bash, git, and curl. Any image with bash will work.',
273
- ].join('\n');
274
- }
275
- function formatWindowsDockerImageNotPulledMessage(image) {
276
- return [
277
- `Sandboxing is enabled, but the Docker image "${image}" is not available`,
278
- 'on this machine yet.',
279
- '',
280
- 'To fix this, open PowerShell (or any terminal) and run:',
281
- ` docker pull ${image}`,
282
- '',
283
- 'This only needs to happen once — Docker caches the image afterwards.',
284
- '',
285
- 'If Docker Desktop is not running, start it from the Start menu first',
286
- 'and wait for the whale icon in your system tray to stop animating.',
287
- ].join('\n');
288
- }
289
272
  /**
290
273
  * Format a one-line log message for spell startup.
291
274
  */