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