@vercel/dream 0.2.7 → 0.2.9

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