@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.
- package/dist/dream.js +233 -107
- 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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
188
|
+
printAdded("specs/2048.md");
|
|
141
189
|
} else {
|
|
142
|
-
|
|
190
|
+
printNote(`specs/ ${dim("already exists")}`);
|
|
143
191
|
}
|
|
144
192
|
if (result.packageJsonCreated) {
|
|
145
|
-
|
|
193
|
+
printAdded("package.json");
|
|
146
194
|
} else {
|
|
147
|
-
|
|
195
|
+
printNote(`package.json ${dim("already exists")}`);
|
|
148
196
|
}
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
164
|
-
|
|
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
|
-
|
|
170
|
-
|
|
216
|
+
printLine(
|
|
217
|
+
`
|
|
218
|
+
${INDENT_MAIN}${dim("specs")} ${dim(`(${specFiles.length})`)}`
|
|
219
|
+
);
|
|
171
220
|
for (const file of specFiles) {
|
|
172
|
-
|
|
221
|
+
printListItem(file);
|
|
173
222
|
}
|
|
174
|
-
|
|
223
|
+
printLine();
|
|
175
224
|
});
|
|
176
225
|
program.command("models").description("List available models and check provider auth").action(async () => {
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
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
|
-
|
|
198
|
-
|
|
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
|
-
|
|
250
|
+
printListItem(`${provider.id}/${id} ${dim(name)}`);
|
|
204
251
|
}
|
|
205
252
|
if (models.length === 0) {
|
|
206
|
-
|
|
253
|
+
printLine(`${INDENT_LIST}${dim("no models")}`);
|
|
207
254
|
}
|
|
208
|
-
|
|
255
|
+
printLine();
|
|
209
256
|
}
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
224
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
);
|
|
299
|
-
log(
|
|
355
|
+
printError(`Provider ${bold(providerId)} is not connected`);
|
|
356
|
+
printKV("connected", connected.length ? connected.join(", ") : "none");
|
|
357
|
+
printLine(
|
|
300
358
|
`
|
|
301
|
-
|
|
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
|
-
|
|
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
|
-
|
|
316
|
-
|
|
371
|
+
printKV(
|
|
372
|
+
"available",
|
|
373
|
+
`${available.slice(0, 5).join(", ")}${available.length > 5 ? ` (+${available.length - 5} more)` : ""}`
|
|
317
374
|
);
|
|
318
375
|
}
|
|
319
|
-
|
|
376
|
+
printLine();
|
|
320
377
|
server.close();
|
|
321
378
|
process.exit(1);
|
|
322
379
|
}
|
|
323
|
-
|
|
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
|
-
|
|
339
|
-
|
|
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
|
-
|
|
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
|
-
|
|
355
|
-
|
|
413
|
+
printLine(
|
|
414
|
+
`${INDENT_MAIN}${cyan(`[${iteration}]`)} ${green("\u2713")} Done ${dim(`(${formatTime(iterElapsed)})`)}`
|
|
356
415
|
);
|
|
357
|
-
|
|
416
|
+
printLine(
|
|
358
417
|
`
|
|
359
|
-
|
|
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
|
-
|
|
424
|
+
printLine(
|
|
366
425
|
`
|
|
367
|
-
|
|
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
|
-
|
|
373
|
-
|
|
431
|
+
printLine(
|
|
432
|
+
`${INDENT_MAIN}${cyan(`[${iteration}]`)} ${dim(`${formatTime(iterElapsed)} \xB7 continuing...`)}
|
|
374
433
|
`
|
|
375
434
|
);
|
|
376
435
|
}
|
|
377
|
-
|
|
378
|
-
|
|
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
|
-
|
|
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
|
-
|
|
397
|
-
|
|
455
|
+
printSessionDim(`session ${sessionId.slice(0, 8)}`);
|
|
456
|
+
printSessionDim("subscribing to events...");
|
|
398
457
|
const events = await client.event.subscribe();
|
|
399
|
-
|
|
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
|
-
|
|
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
|
-
|
|
478
|
+
printSessionDim(`event: ${event.type} [${sid}]`);
|
|
423
479
|
if (event.type !== "server.connected") {
|
|
424
|
-
|
|
480
|
+
printLine(
|
|
425
481
|
dim(
|
|
426
|
-
|
|
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
|
-
${
|
|
497
|
+
${INDENT_SESSION}`);
|
|
442
498
|
process.stdout.write(
|
|
443
|
-
lastOutput !== "text" ? `${
|
|
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
|
-
${
|
|
506
|
+
${INDENT_SESSION}`);
|
|
451
507
|
process.stdout.write(
|
|
452
|
-
lastOutput !== "text" ? `${
|
|
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
|
-
|
|
466
|
-
`${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
562
|
+
printSessionDim(`${toolCalls} tools \xB7 ${tokens}${cost}`);
|
|
507
563
|
if (responseText.length === 0) {
|
|
508
|
-
|
|
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.
|
|
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": {
|