@vercel/dream 0.2.7 → 0.2.8

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 (2) hide show
  1. package/dist/dream.js +221 -106
  2. package/package.json +2 -2
package/dist/dream.js CHANGED
@@ -3,9 +3,11 @@
3
3
  // bin/dream.ts
4
4
  import * as fs from "fs";
5
5
  import * as path from "path";
6
+ import { fileURLToPath } from "url";
6
7
  import { createOpencode } from "@opencode-ai/sdk/v2";
7
8
  import { init } from "@vercel/dream-init";
8
9
  import { program } from "commander";
10
+ import { $ } from "zx";
9
11
  var STOP_WORD = "<DREAM DONE>";
10
12
  var PROJECT_PROMPT_FILES = [
11
13
  "loop-prompt.md",
@@ -87,7 +89,7 @@ Context for next iteration.
87
89
 
88
90
  ## Browser Automation
89
91
 
90
- You have \`agent-browser\` available for UI verification.
92
+ Dream bootstraps \`agent-browser\` before the loop starts. Use it for UI verification.
91
93
 
92
94
  Use it to test running app routes (typically localhost dev server or preview URL), not only static files.
93
95
 
@@ -122,62 +124,106 @@ ${STOP_WORD}
122
124
  Without this signal, the system keeps launching new iterations.`;
123
125
  var DEFAULT_TIMEOUT = 36e5;
124
126
  var DEFAULT_MAX_ITERATIONS = 100;
125
- var DEFAULT_MODEL = "vercel/anthropic/claude-opus-4.5";
127
+ var DEFAULT_MODEL = "vercel/anthropic/claude-opus-4.6";
128
+ var DEFAULT_BOOTSTRAP_SKILL_REPOS = [
129
+ { repo: "vercel-labs/agent-browser", skills: ["agent-browser"] },
130
+ { repo: "vercel-labs/agent-skills", skills: ["react-best-practices"] }
131
+ ];
126
132
  var dim = (s) => `\x1B[2m${s}\x1B[22m`;
127
133
  var bold = (s) => `\x1B[1m${s}\x1B[22m`;
128
134
  var green = (s) => `\x1B[32m${s}\x1B[39m`;
129
135
  var red = (s) => `\x1B[31m${s}\x1B[39m`;
130
136
  var cyan = (s) => `\x1B[36m${s}\x1B[39m`;
131
137
  var log = console.log;
138
+ var INDENT_MAIN = " ";
139
+ var INDENT_LIST = " ";
140
+ var INDENT_SESSION = " ";
141
+ var INDENT_SESSION_DETAIL = " ";
142
+ var KEY_WIDTH = 13;
143
+ function printLine(line = "") {
144
+ log(line);
145
+ }
146
+ function printTitle(topic) {
147
+ printLine(`
148
+ ${INDENT_MAIN}${bold("\u25B2 dream")} ${dim("\xB7")} ${topic}
149
+ `);
150
+ }
151
+ function printKV(key, value) {
152
+ printLine(`${INDENT_MAIN}${dim(key.padEnd(KEY_WIDTH))}${value}`);
153
+ }
154
+ function printStep(message) {
155
+ printLine(`${INDENT_MAIN}${dim("\u25CC")} ${message}`);
156
+ }
157
+ function printNote(message) {
158
+ printLine(`${INDENT_MAIN}${dim("\xB7")} ${message}`);
159
+ }
160
+ function printListItem(message) {
161
+ printLine(`${INDENT_LIST}${dim("\xB7")} ${message}`);
162
+ }
163
+ function printSuccess(message) {
164
+ printLine(`${INDENT_MAIN}${green("\u25CF")} ${message}`);
165
+ }
166
+ function printError(message) {
167
+ printLine(`${INDENT_MAIN}${red("\u2717")} ${message}`);
168
+ }
169
+ function printAdded(message) {
170
+ printLine(`${INDENT_MAIN}${green("+")} ${message}`);
171
+ }
172
+ function printSession(message) {
173
+ printLine(`${INDENT_SESSION}${message}`);
174
+ }
175
+ function printSessionDim(message) {
176
+ printSession(dim(message));
177
+ }
178
+ function printSessionError(message) {
179
+ printSession(`${red("\u2717")} ${message}`);
180
+ }
132
181
  program.name("dream").description("Run OpenCode in a loop until specs are complete").version("0.1.0").option("-d, --dir <directory>", "Working directory", ".");
133
182
  program.command("init").description("Initialize a new dream project").action(() => {
134
183
  const workDir = path.resolve(program.opts().dir);
135
- log(`
136
- ${bold("\u25B2 dream")} ${dim("\xB7 init")}
137
- `);
184
+ printTitle("init");
138
185
  const result = init({ dir: workDir, version: "^0.2.1" });
139
186
  if (result.specsCreated) {
140
- log(` ${green("+")} specs/2048.md`);
187
+ printAdded("specs/2048.md");
141
188
  } else {
142
- log(` ${dim("\xB7")} specs/ ${dim("already exists")}`);
189
+ printNote(`specs/ ${dim("already exists")}`);
143
190
  }
144
191
  if (result.packageJsonCreated) {
145
- log(` ${green("+")} package.json`);
192
+ printAdded("package.json");
146
193
  } else {
147
- log(` ${dim("\xB7")} package.json ${dim("already exists")}`);
194
+ printNote(`package.json ${dim("already exists")}`);
148
195
  }
149
- log(`
150
- Run ${cyan("pnpm install")} then ${cyan("dream")} to start
151
- `);
196
+ printLine(
197
+ `
198
+ ${INDENT_MAIN}Run ${cyan("pnpm install")} then ${cyan("dream")} to start
199
+ `
200
+ );
152
201
  });
153
202
  program.command("config").description("Show project configuration and specs").action(() => {
154
203
  const workDir = path.resolve(program.opts().dir);
155
204
  const specsDir = path.join(workDir, "specs");
156
- log(`
157
- ${bold("\u25B2 dream")} ${dim("\xB7 config")}
158
- `);
159
- log(` ${dim("dir")} ${workDir}`);
160
- log(` ${dim("timeout")} ${formatTime(DEFAULT_TIMEOUT)}`);
161
- log(` ${dim("max")} ${DEFAULT_MAX_ITERATIONS} iterations`);
205
+ printTitle("config");
206
+ printKV("dir", workDir);
207
+ printKV("timeout", formatTime(DEFAULT_TIMEOUT));
208
+ printKV("max", `${DEFAULT_MAX_ITERATIONS} iterations`);
162
209
  if (!fs.existsSync(specsDir)) {
163
- log(`
164
- ${red("\u2717")} specs/ not found
165
- `);
210
+ printError("specs/ not found");
211
+ printLine();
166
212
  return;
167
213
  }
168
214
  const specFiles = fs.readdirSync(specsDir).filter((f) => f.endsWith(".md"));
169
- log(`
170
- ${dim("specs")} ${dim(`(${specFiles.length})`)}`);
215
+ printLine(
216
+ `
217
+ ${INDENT_MAIN}${dim("specs")} ${dim(`(${specFiles.length})`)}`
218
+ );
171
219
  for (const file of specFiles) {
172
- log(` ${dim("\xB7")} ${file}`);
220
+ printListItem(file);
173
221
  }
174
- log("");
222
+ printLine();
175
223
  });
176
224
  program.command("models").description("List available models and check provider auth").action(async () => {
177
- log(`
178
- ${bold("\u25B2 dream")} ${dim("\xB7 models")}
179
- `);
180
- log(` ${dim("\u25CC")} Starting OpenCode...`);
225
+ printTitle("models");
226
+ printStep("Starting OpenCode...");
181
227
  const { client, server } = await createOpencode({
182
228
  port: 0,
183
229
  config: { enabled_providers: ["vercel"] }
@@ -185,8 +231,8 @@ program.command("models").description("List available models and check provider
185
231
  try {
186
232
  const res = await client.provider.list();
187
233
  if (res.error) {
188
- log(` ${red("\u2717")} Failed to list providers
189
- `);
234
+ printError("Failed to list providers");
235
+ printLine();
190
236
  return;
191
237
  }
192
238
  const { all, connected } = res.data;
@@ -194,34 +240,32 @@ program.command("models").description("List available models and check provider
194
240
  const isConnected = connected.includes(provider.id);
195
241
  const icon = isConnected ? green("\u25CF") : red("\u25CB");
196
242
  const npm = provider.npm ? dim(` npm:${provider.npm}`) : "";
197
- log(
198
- ` ${icon} ${bold(provider.name)} ${dim(`(${provider.id})`)}${npm}`
243
+ printLine(
244
+ `${INDENT_MAIN}${icon} ${bold(provider.name)} ${dim(`(${provider.id})`)}${npm}`
199
245
  );
200
246
  const models = Object.entries(provider.models);
201
247
  for (const [id, model] of models) {
202
248
  const name = model.name ?? id;
203
- log(` ${dim("\xB7")} ${provider.id}/${id} ${dim(name)}`);
249
+ printListItem(`${provider.id}/${id} ${dim(name)}`);
204
250
  }
205
251
  if (models.length === 0) {
206
- log(` ${dim("no models")}`);
252
+ printLine(`${INDENT_LIST}${dim("no models")}`);
207
253
  }
208
- log("");
254
+ printLine();
209
255
  }
210
- log(
211
- ` ${dim("connected")} ${connected.length ? connected.join(", ") : "none"}
212
- `
213
- );
256
+ printKV("connected", connected.length ? connected.join(", ") : "none");
257
+ printLine();
214
258
  } finally {
215
259
  server.close();
216
260
  process.exit(0);
217
261
  }
218
262
  });
219
- program.option("-m, --model <model>", "Model to use (provider/model format)").option("-t, --timeout <ms>", "Timeout in milliseconds").option("-i, --max-iterations <n>", "Maximum iterations").option("-v, --verbose", "Verbose output").action(async (opts) => {
263
+ program.option("-m, --model <model>", "Model to use (provider/model format)").option("-t, --timeout <ms>", "Timeout in milliseconds").option("-i, --max-iterations <n>", "Maximum iterations").option("-v, --verbose", "Verbose output").option("--skip-bootstrap", "Skip runtime bootstrap").action(async (opts) => {
220
264
  const workDir = path.resolve(opts.dir);
221
265
  const specsDir = path.join(workDir, "specs");
222
266
  if (!fs.existsSync(specsDir)) {
223
- log(`
224
- ${red("\u2717")} specs/ not found in ${workDir}
267
+ printLine(`
268
+ ${INDENT_MAIN}${red("\u2717")} specs/ not found in ${workDir}
225
269
  `);
226
270
  process.exit(1);
227
271
  }
@@ -236,18 +280,32 @@ program.option("-m, --model <model>", "Model to use (provider/model format)").op
236
280
  ## Project Prompt Overlay (${projectPrompt.file})
237
281
 
238
282
  ${projectPrompt.content}` : SYSTEM_PROMPT;
239
- log(`
240
- ${bold("\u25B2 dream")} ${dim("\xB7")} ${title}
241
- `);
242
- log(` ${dim("dir")} ${workDir}`);
243
- log(` ${dim("model")} ${model || dim("default")}`);
244
- log(` ${dim("timeout")} ${formatTime(timeout)}`);
245
- log(` ${dim("max")} ${maxIterations} iterations
246
- `);
283
+ printTitle(title);
284
+ printKV("dir", workDir);
285
+ printKV("model", model || dim("default"));
286
+ printKV("timeout", formatTime(timeout));
287
+ printKV("max", `${maxIterations} iterations`);
288
+ printLine();
247
289
  if (projectPrompt) {
248
- log(` ${dim("overlay")} ${projectPrompt.file}`);
290
+ printKV("overlay", projectPrompt.file);
249
291
  }
250
- log(` ${dim("\u25CC")} Starting OpenCode...`);
292
+ if (opts.skipBootstrap) {
293
+ printKV("bootstrap", dim("skipped"));
294
+ }
295
+ if (!opts.skipBootstrap) {
296
+ try {
297
+ await bootstrapRuntime(workDir);
298
+ } catch (error) {
299
+ const message = error instanceof Error ? error.message : "bootstrap failed";
300
+ printLine(
301
+ `
302
+ ${INDENT_MAIN}${red("\u2717")} Runtime bootstrap failed: ${message}
303
+ `
304
+ );
305
+ process.exit(1);
306
+ }
307
+ }
308
+ printStep("Starting OpenCode...");
251
309
  const oidcToken = process.env.VERCEL_OIDC_TOKEN;
252
310
  const { client, server } = await createOpencode({
253
311
  port: 0,
@@ -280,25 +338,23 @@ ${projectPrompt.content}` : SYSTEM_PROMPT;
280
338
  enabled_providers: ["vercel"]
281
339
  }
282
340
  });
283
- log(` ${green("\u25CF")} OpenCode ready`);
341
+ printSuccess("OpenCode ready");
284
342
  const providerId = model?.split("/")[0];
285
343
  if (providerId) {
286
344
  const providers = await client.provider.list();
287
345
  if (providers.error) {
288
- log(` ${red("\u2717")} Failed to list providers
289
- `);
346
+ printError("Failed to list providers");
347
+ printLine();
290
348
  server.close();
291
349
  process.exit(1);
292
350
  }
293
351
  const connected = providers.data.connected ?? [];
294
352
  if (!connected.includes(providerId)) {
295
- log(` ${red("\u2717")} Provider ${bold(providerId)} is not connected`);
296
- log(
297
- ` ${dim("connected")} ${connected.length ? connected.join(", ") : "none"}`
298
- );
299
- log(
353
+ printError(`Provider ${bold(providerId)} is not connected`);
354
+ printKV("connected", connected.length ? connected.join(", ") : "none");
355
+ printLine(
300
356
  `
301
- Run ${cyan("opencode")} and authenticate the ${bold(providerId)} provider
357
+ ${INDENT_MAIN}Run ${cyan("opencode")} and authenticate the ${bold(providerId)} provider
302
358
  `
303
359
  );
304
360
  server.close();
@@ -307,21 +363,20 @@ ${projectPrompt.content}` : SYSTEM_PROMPT;
307
363
  const provider = providers.data.all.find((p) => p.id === providerId);
308
364
  const modelId = model.split("/").slice(1).join("/");
309
365
  if (provider && modelId && !provider.models[modelId]) {
310
- log(
311
- ` ${red("\u2717")} Model ${bold(modelId)} not found in ${bold(providerId)}`
312
- );
366
+ printError(`Model ${bold(modelId)} not found in ${bold(providerId)}`);
313
367
  const available = Object.keys(provider.models);
314
368
  if (available.length) {
315
- log(
316
- ` ${dim("available")} ${available.slice(0, 5).join(", ")}${available.length > 5 ? ` (+${available.length - 5} more)` : ""}`
369
+ printKV(
370
+ "available",
371
+ `${available.slice(0, 5).join(", ")}${available.length > 5 ? ` (+${available.length - 5} more)` : ""}`
317
372
  );
318
373
  }
319
- log("");
374
+ printLine();
320
375
  server.close();
321
376
  process.exit(1);
322
377
  }
323
- log(` ${green("\u25CF")} Provider ${bold(providerId)} connected
324
- `);
378
+ printSuccess(`Provider ${bold(providerId)} connected`);
379
+ printLine();
325
380
  }
326
381
  const cleanup = () => {
327
382
  server.close();
@@ -335,14 +390,16 @@ ${projectPrompt.content}` : SYSTEM_PROMPT;
335
390
  while (iteration < maxIterations) {
336
391
  const elapsed = Date.now() - startTime;
337
392
  if (elapsed >= timeout) {
338
- log(`
339
- ${red("\u2717")} Timeout after ${formatTime(elapsed)}
340
- `);
393
+ printLine(
394
+ `
395
+ ${INDENT_MAIN}${red("\u2717")} Timeout after ${formatTime(elapsed)}
396
+ `
397
+ );
341
398
  process.exit(1);
342
399
  }
343
400
  iteration++;
344
401
  const iterStart = Date.now();
345
- log(` ${cyan(`[${iteration}]`)} Running session...`);
402
+ printLine(`${INDENT_MAIN}${cyan(`[${iteration}]`)} Running session...`);
346
403
  const result = await runSession(
347
404
  client,
348
405
  title,
@@ -351,31 +408,31 @@ ${projectPrompt.content}` : SYSTEM_PROMPT;
351
408
  );
352
409
  const iterElapsed = Date.now() - iterStart;
353
410
  if (result === "done") {
354
- log(
355
- ` ${cyan(`[${iteration}]`)} ${green("\u2713")} Done ${dim(`(${formatTime(iterElapsed)})`)}`
411
+ printLine(
412
+ `${INDENT_MAIN}${cyan(`[${iteration}]`)} ${green("\u2713")} Done ${dim(`(${formatTime(iterElapsed)})`)}`
356
413
  );
357
- log(
414
+ printLine(
358
415
  `
359
- ${green("\u2713")} Completed in ${bold(String(iteration))} iteration(s) ${dim(`(${formatTime(Date.now() - startTime)})`)}
416
+ ${INDENT_MAIN}${green("\u2713")} Completed in ${bold(String(iteration))} iteration(s) ${dim(`(${formatTime(Date.now() - startTime)})`)}
360
417
  `
361
418
  );
362
419
  process.exit(0);
363
420
  }
364
421
  if (result === "error") {
365
- log(
422
+ printLine(
366
423
  `
367
- ${red("\u2717")} Session failed after ${bold(String(iteration))} iteration(s) ${dim(`(${formatTime(Date.now() - startTime)})`)}
424
+ ${INDENT_MAIN}${red("\u2717")} Session failed after ${bold(String(iteration))} iteration(s) ${dim(`(${formatTime(Date.now() - startTime)})`)}
368
425
  `
369
426
  );
370
427
  process.exit(1);
371
428
  }
372
- log(
373
- ` ${cyan(`[${iteration}]`)} ${dim(`${formatTime(iterElapsed)} \xB7 continuing...`)}
429
+ printLine(
430
+ `${INDENT_MAIN}${cyan(`[${iteration}]`)} ${dim(`${formatTime(iterElapsed)} \xB7 continuing...`)}
374
431
  `
375
432
  );
376
433
  }
377
- log(`
378
- ${red("\u2717")} Max iterations reached
434
+ printLine(`
435
+ ${INDENT_MAIN}${red("\u2717")} Max iterations reached
379
436
  `);
380
437
  process.exit(1);
381
438
  } finally {
@@ -383,7 +440,7 @@ ${projectPrompt.content}` : SYSTEM_PROMPT;
383
440
  }
384
441
  });
385
442
  async function runSession(client, title, systemPrompt, verbose) {
386
- log(` ${dim("creating session...")}`);
443
+ printSessionDim("creating session...");
387
444
  const sessionResponse = await client.session.create({
388
445
  title: `Dream: ${title}`
389
446
  });
@@ -393,18 +450,16 @@ async function runSession(client, title, systemPrompt, verbose) {
393
450
  );
394
451
  }
395
452
  const sessionId = sessionResponse.data.id;
396
- log(` ${dim(`session ${sessionId.slice(0, 8)}`)}`);
397
- log(` ${dim("subscribing to events...")}`);
453
+ printSessionDim(`session ${sessionId.slice(0, 8)}`);
454
+ printSessionDim("subscribing to events...");
398
455
  const events = await client.event.subscribe();
399
- log(` ${dim("sending prompt...")}`);
456
+ printSessionDim("sending prompt...");
400
457
  const promptResponse = await client.session.promptAsync({
401
458
  sessionID: sessionId,
402
459
  parts: [{ type: "text", text: systemPrompt }]
403
460
  });
404
461
  if (promptResponse.error) {
405
- log(
406
- ` ${red("\u2717")} prompt error: ${JSON.stringify(promptResponse.error)}`
407
- );
462
+ printSessionError(`prompt error: ${JSON.stringify(promptResponse.error)}`);
408
463
  return "error";
409
464
  }
410
465
  let responseText = "";
@@ -414,16 +469,15 @@ async function runSession(client, title, systemPrompt, verbose) {
414
469
  let totalTokensOut = 0;
415
470
  const seenTools = /* @__PURE__ */ new Set();
416
471
  let lastOutput = "none";
417
- const pad = " ";
418
472
  for await (const event of events.stream) {
419
473
  const props = event.properties;
420
474
  if (verbose) {
421
475
  const sid = props.sessionID ? props.sessionID.slice(0, 8) : "global";
422
- log(dim(` event: ${event.type} [${sid}]`));
476
+ printSessionDim(`event: ${event.type} [${sid}]`);
423
477
  if (event.type !== "server.connected") {
424
- log(
478
+ printLine(
425
479
  dim(
426
- ` ${JSON.stringify(event.properties).slice(0, 200)}`
480
+ `${INDENT_SESSION_DETAIL}${JSON.stringify(event.properties).slice(0, 200)}`
427
481
  )
428
482
  );
429
483
  }
@@ -438,18 +492,18 @@ async function runSession(client, title, systemPrompt, verbose) {
438
492
  responseText += delta;
439
493
  if (lastOutput === "tool") process.stdout.write("\n");
440
494
  const indented = delta.replace(/\n/g, `
441
- ${pad}`);
495
+ ${INDENT_SESSION}`);
442
496
  process.stdout.write(
443
- lastOutput !== "text" ? `${pad}${dim(indented)}` : dim(indented)
497
+ lastOutput !== "text" ? `${INDENT_SESSION}${dim(indented)}` : dim(indented)
444
498
  );
445
499
  lastOutput = "text";
446
500
  }
447
501
  if (part.type === "reasoning" && delta) {
448
502
  if (lastOutput === "tool") process.stdout.write("\n");
449
503
  const indented = delta.replace(/\n/g, `
450
- ${pad}`);
504
+ ${INDENT_SESSION}`);
451
505
  process.stdout.write(
452
- lastOutput !== "text" ? `${pad}${dim(indented)}` : dim(indented)
506
+ lastOutput !== "text" ? `${INDENT_SESSION}${dim(indented)}` : dim(indented)
453
507
  );
454
508
  lastOutput = "text";
455
509
  }
@@ -462,14 +516,14 @@ ${pad}`);
462
516
  toolCalls++;
463
517
  if (lastOutput === "text") process.stdout.write("\n\n");
464
518
  const context = toolContext(toolName, state.input) ?? state.title;
465
- log(
466
- `${pad}${dim("\u25B8")} ${toolName}${context ? dim(` ${context}`) : ""}`
519
+ printSession(
520
+ `${dim("\u25B8")} ${toolName}${context ? dim(` ${context}`) : ""}`
467
521
  );
468
522
  lastOutput = "tool";
469
523
  }
470
524
  if (state.status === "error") {
471
525
  if (lastOutput === "text") process.stdout.write("\n");
472
- log(`${pad}${red("\u2717")} ${toolName}: ${state.error}`);
526
+ printSession(`${red("\u2717")} ${toolName}: ${state.error}`);
473
527
  lastOutput = "tool";
474
528
  }
475
529
  }
@@ -485,7 +539,7 @@ ${pad}`);
485
539
  if (file) {
486
540
  if (lastOutput === "text") process.stdout.write("\n");
487
541
  const relative = file.replace(`${process.cwd()}/`, "");
488
- log(`${pad}${green("\u270E")} ${relative}`);
542
+ printSession(`${green("\u270E")} ${relative}`);
489
543
  lastOutput = "tool";
490
544
  }
491
545
  }
@@ -493,7 +547,7 @@ ${pad}`);
493
547
  const errProps = event.properties;
494
548
  const msg = errProps.error?.data?.message ?? errProps.error?.name ?? "session error";
495
549
  if (lastOutput === "text") process.stdout.write("\n");
496
- log(`${pad}${red("\u2717")} ${msg}`);
550
+ printSession(`${red("\u2717")} ${msg}`);
497
551
  return "error";
498
552
  }
499
553
  if (event.type === "session.idle") {
@@ -503,9 +557,9 @@ ${pad}`);
503
557
  if (lastOutput === "text") process.stdout.write("\n");
504
558
  const tokens = `${formatTokens(totalTokensIn)}\u2192${formatTokens(totalTokensOut)}`;
505
559
  const cost = totalCost > 0 ? ` \xB7 $${totalCost.toFixed(2)}` : "";
506
- log(`${pad}${dim(`${toolCalls} tools \xB7 ${tokens}${cost}`)}`);
560
+ printSessionDim(`${toolCalls} tools \xB7 ${tokens}${cost}`);
507
561
  if (responseText.length === 0) {
508
- log(`${pad}${red("\u2717")} No response from model`);
562
+ printSessionError("No response from model");
509
563
  return "error";
510
564
  }
511
565
  return responseText.includes(STOP_WORD) ? "done" : "continue";
@@ -553,4 +607,65 @@ function loadProjectPrompt(specsDir) {
553
607
  }
554
608
  return null;
555
609
  }
610
+ function packageRootFromScript() {
611
+ const scriptPath = fileURLToPath(import.meta.url);
612
+ return path.resolve(path.dirname(scriptPath), "..");
613
+ }
614
+ function prependLocalBinsToPath(workDir) {
615
+ const packageRoot = packageRootFromScript();
616
+ const binPaths = [
617
+ path.join(workDir, "node_modules", ".bin"),
618
+ path.join(packageRoot, "node_modules", ".bin")
619
+ ];
620
+ const currentPath = process.env.PATH ?? "";
621
+ process.env.PATH = [...binPaths, currentPath].filter((entry) => Boolean(entry)).join(path.delimiter);
622
+ }
623
+ async function commandExists(command) {
624
+ try {
625
+ await $`command -v ${command}`;
626
+ return true;
627
+ } catch {
628
+ return false;
629
+ }
630
+ }
631
+ async function installSkills(skillRepos) {
632
+ for (const entry of skillRepos) {
633
+ const skills = entry.skills?.filter((skill) => skill.trim().length > 0) ?? [];
634
+ if (skills.length === 0) {
635
+ printNote(`Installing skills from ${entry.repo}...`);
636
+ await $`npx skills@latest add ${entry.repo} --yes --global`;
637
+ continue;
638
+ }
639
+ for (const skill of skills) {
640
+ printNote(`Installing skill ${skill} from ${entry.repo}...`);
641
+ await $`npx skills@latest add ${entry.repo} --skill ${skill} --yes --global`;
642
+ }
643
+ }
644
+ }
645
+ async function bootstrapRuntime(workDir) {
646
+ prependLocalBinsToPath(workDir);
647
+ printStep("Bootstrapping runtime tools...");
648
+ let hasAgentBrowser = await commandExists("agent-browser");
649
+ if (!hasAgentBrowser) {
650
+ printNote("Installing agent-browser globally...");
651
+ await $`npm install --global agent-browser`;
652
+ hasAgentBrowser = await commandExists("agent-browser");
653
+ }
654
+ printNote("Installing browser runtime...");
655
+ if (hasAgentBrowser) {
656
+ await $`agent-browser install`;
657
+ } else {
658
+ printNote("Falling back to npx for agent-browser install...");
659
+ await $`npx --yes agent-browser install`;
660
+ }
661
+ if (process.env.DREAM_SKIP_SKILL_SETUP === "1") {
662
+ printNote("Skipping skill setup (DREAM_SKIP_SKILL_SETUP=1)");
663
+ return;
664
+ }
665
+ if (DEFAULT_BOOTSTRAP_SKILL_REPOS.length === 0) {
666
+ printNote("No bootstrap skills configured");
667
+ return;
668
+ }
669
+ await installSkills(DEFAULT_BOOTSTRAP_SKILL_REPOS);
670
+ }
556
671
  await program.parseAsync();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/dream",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "A CLI that runs OpenCode in a loop until specs are complete",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,9 +12,9 @@
12
12
  "dependencies": {
13
13
  "@ai-sdk/gateway": "^3.0.39",
14
14
  "@opencode-ai/sdk": "^1.1.0",
15
- "agent-browser": ">=0.9.0",
16
15
  "commander": "^12.0.0",
17
16
  "opencode-ai": ">=1.0.0",
17
+ "zx": "^8.8.4",
18
18
  "@vercel/dream-init": "0.2.2"
19
19
  },
20
20
  "devDependencies": {