create-volt 0.49.0 → 0.50.0

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/CHANGELOG.md CHANGED
@@ -4,6 +4,15 @@ All notable changes to `create-volt` are documented here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/), and this project adheres to
5
5
  [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [0.50.0] - 2026-06-30
8
+
9
+ ### Added
10
+ - **Config editor AI falls back to the hosted gateway.** With no local provider
11
+ key, `/setup/ai` routes through the voltjs.com gateway via `VOLT_AI_TOKEN`
12
+ (free-capped, then pay-as-you-go on the host's key); a local key still wins
13
+ (BYO). Clear error when neither is set. The gateway now honors the client's
14
+ stream preference, so the editor gets a normal JSON response.
15
+
7
16
  ## [0.49.0] - 2026-06-29
8
17
 
9
18
  ### Added
@@ -657,6 +666,7 @@ All notable changes to `create-volt` are documented here. The format follows
657
666
  watching and full-page hot reload. Supports `--skip-install` and `--force`,
658
667
  and auto-detects npm / pnpm / yarn / bun for the install step.
659
668
 
669
+ [0.50.0]: https://github.com/MIR-2025/volt/releases/tag/v0.50.0
660
670
  [0.49.0]: https://github.com/MIR-2025/volt/releases/tag/v0.49.0
661
671
  [0.48.3]: https://github.com/MIR-2025/volt/releases/tag/v0.48.3
662
672
  [0.48.2]: https://github.com/MIR-2025/volt/releases/tag/v0.48.2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-volt",
3
- "version": "0.49.0",
3
+ "version": "0.50.0",
4
4
  "description": "Scaffold a new Volt app — no-build, signals-based UI with Socket.io hot reload.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -344,9 +344,10 @@ function startSetup() {
344
344
  }
345
345
  return;
346
346
  }
347
- // --- AI proxy for the in-config editor (RTEPro). Injects the .env provider
348
- // key server-side (never reaches the browser). BYO keys; RTEPro POSTs a
349
- // provider-native body with a _provider field. ---
347
+ // --- AI proxy for the in-config editor (RTEPro). Uses a local provider key
348
+ // (BYO) when set; otherwise falls back to the voltjs.com gateway via
349
+ // VOLT_AI_TOKEN (free-capped, then pay-as-you-go on the host's key). The
350
+ // key/token never reaches the browser. ---
350
351
  if (req.method === "POST" && p === "/setup/ai") {
351
352
  let cbody = "";
352
353
  req.on("data", (c) => (cbody += c));
@@ -357,23 +358,30 @@ function startSetup() {
357
358
  const body = JSON.parse(cbody || "{}");
358
359
  const provider = body._provider || env.AI_PROVIDER || "anthropic";
359
360
  delete body._provider;
360
- let url, headers;
361
- if (provider === "anthropic") {
362
- if (!env.ANTHROPIC_API_KEY) throw new Error("ANTHROPIC_API_KEY not set in .env");
363
- url = "https://api.anthropic.com/v1/messages";
364
- headers = { "x-api-key": env.ANTHROPIC_API_KEY, "anthropic-version": "2023-06-01", "content-type": "application/json" };
365
- } else if (provider === "openai") {
366
- if (!env.OPENAI_API_KEY) throw new Error("OPENAI_API_KEY not set in .env");
367
- url = "https://api.openai.com/v1/chat/completions";
368
- headers = { authorization: "Bearer " + env.OPENAI_API_KEY, "content-type": "application/json" };
369
- } else if (provider === "gemini") {
370
- if (!env.GEMINI_API_KEY) throw new Error("GEMINI_API_KEY not set in .env");
371
- const model = body.model || "gemini-2.0-flash";
372
- delete body.model;
373
- url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${env.GEMINI_API_KEY}`;
374
- headers = { "content-type": "application/json" };
375
- } else throw new Error("unknown AI provider: " + provider);
376
- const r = await fetch(url, { method: "POST", headers, body: JSON.stringify(body) });
361
+ const localKey = { anthropic: env.ANTHROPIC_API_KEY, openai: env.OPENAI_API_KEY, gemini: env.GEMINI_API_KEY }[provider];
362
+ let url, headers, payload = body;
363
+ if (localKey) {
364
+ if (provider === "anthropic") {
365
+ url = "https://api.anthropic.com/v1/messages";
366
+ headers = { "x-api-key": localKey, "anthropic-version": "2023-06-01", "content-type": "application/json" };
367
+ } else if (provider === "openai") {
368
+ url = "https://api.openai.com/v1/chat/completions";
369
+ headers = { authorization: "Bearer " + localKey, "content-type": "application/json" };
370
+ } else if (provider === "gemini") {
371
+ const model = body.model || "gemini-2.0-flash";
372
+ delete body.model;
373
+ url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${localKey}`;
374
+ headers = { "content-type": "application/json" };
375
+ } else throw new Error("unknown AI provider: " + provider);
376
+ } else if (env.VOLT_AI_TOKEN) {
377
+ // no local key host gateway (free-capped, then pay-as-you-go)
378
+ url = env.VOLT_AI_GATEWAY || "https://voltjs.com/api/ai";
379
+ headers = { authorization: "Bearer " + env.VOLT_AI_TOKEN, "content-type": "application/json" };
380
+ payload = { messages: body.messages, system: body.system, max_tokens: body.max_tokens, model: body.model };
381
+ } else {
382
+ throw new Error("No AI key in .env and no VOLT_AI_TOKEN — add a provider key, or a gateway token to use the hosted tier.");
383
+ }
384
+ const r = await fetch(url, { method: "POST", headers, body: JSON.stringify(payload) });
377
385
  const text = await r.text();
378
386
  res.statusCode = r.status;
379
387
  res.setHeader("Content-Type", r.headers.get("content-type") || "application/json");
@@ -344,9 +344,10 @@ function startSetup() {
344
344
  }
345
345
  return;
346
346
  }
347
- // --- AI proxy for the in-config editor (RTEPro). Injects the .env provider
348
- // key server-side (never reaches the browser). BYO keys; RTEPro POSTs a
349
- // provider-native body with a _provider field. ---
347
+ // --- AI proxy for the in-config editor (RTEPro). Uses a local provider key
348
+ // (BYO) when set; otherwise falls back to the voltjs.com gateway via
349
+ // VOLT_AI_TOKEN (free-capped, then pay-as-you-go on the host's key). The
350
+ // key/token never reaches the browser. ---
350
351
  if (req.method === "POST" && p === "/setup/ai") {
351
352
  let cbody = "";
352
353
  req.on("data", (c) => (cbody += c));
@@ -357,23 +358,30 @@ function startSetup() {
357
358
  const body = JSON.parse(cbody || "{}");
358
359
  const provider = body._provider || env.AI_PROVIDER || "anthropic";
359
360
  delete body._provider;
360
- let url, headers;
361
- if (provider === "anthropic") {
362
- if (!env.ANTHROPIC_API_KEY) throw new Error("ANTHROPIC_API_KEY not set in .env");
363
- url = "https://api.anthropic.com/v1/messages";
364
- headers = { "x-api-key": env.ANTHROPIC_API_KEY, "anthropic-version": "2023-06-01", "content-type": "application/json" };
365
- } else if (provider === "openai") {
366
- if (!env.OPENAI_API_KEY) throw new Error("OPENAI_API_KEY not set in .env");
367
- url = "https://api.openai.com/v1/chat/completions";
368
- headers = { authorization: "Bearer " + env.OPENAI_API_KEY, "content-type": "application/json" };
369
- } else if (provider === "gemini") {
370
- if (!env.GEMINI_API_KEY) throw new Error("GEMINI_API_KEY not set in .env");
371
- const model = body.model || "gemini-2.0-flash";
372
- delete body.model;
373
- url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${env.GEMINI_API_KEY}`;
374
- headers = { "content-type": "application/json" };
375
- } else throw new Error("unknown AI provider: " + provider);
376
- const r = await fetch(url, { method: "POST", headers, body: JSON.stringify(body) });
361
+ const localKey = { anthropic: env.ANTHROPIC_API_KEY, openai: env.OPENAI_API_KEY, gemini: env.GEMINI_API_KEY }[provider];
362
+ let url, headers, payload = body;
363
+ if (localKey) {
364
+ if (provider === "anthropic") {
365
+ url = "https://api.anthropic.com/v1/messages";
366
+ headers = { "x-api-key": localKey, "anthropic-version": "2023-06-01", "content-type": "application/json" };
367
+ } else if (provider === "openai") {
368
+ url = "https://api.openai.com/v1/chat/completions";
369
+ headers = { authorization: "Bearer " + localKey, "content-type": "application/json" };
370
+ } else if (provider === "gemini") {
371
+ const model = body.model || "gemini-2.0-flash";
372
+ delete body.model;
373
+ url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${localKey}`;
374
+ headers = { "content-type": "application/json" };
375
+ } else throw new Error("unknown AI provider: " + provider);
376
+ } else if (env.VOLT_AI_TOKEN) {
377
+ // no local key host gateway (free-capped, then pay-as-you-go)
378
+ url = env.VOLT_AI_GATEWAY || "https://voltjs.com/api/ai";
379
+ headers = { authorization: "Bearer " + env.VOLT_AI_TOKEN, "content-type": "application/json" };
380
+ payload = { messages: body.messages, system: body.system, max_tokens: body.max_tokens, model: body.model };
381
+ } else {
382
+ throw new Error("No AI key in .env and no VOLT_AI_TOKEN — add a provider key, or a gateway token to use the hosted tier.");
383
+ }
384
+ const r = await fetch(url, { method: "POST", headers, body: JSON.stringify(payload) });
377
385
  const text = await r.text();
378
386
  res.statusCode = r.status;
379
387
  res.setHeader("Content-Type", r.headers.get("content-type") || "application/json");
@@ -344,9 +344,10 @@ function startSetup() {
344
344
  }
345
345
  return;
346
346
  }
347
- // --- AI proxy for the in-config editor (RTEPro). Injects the .env provider
348
- // key server-side (never reaches the browser). BYO keys; RTEPro POSTs a
349
- // provider-native body with a _provider field. ---
347
+ // --- AI proxy for the in-config editor (RTEPro). Uses a local provider key
348
+ // (BYO) when set; otherwise falls back to the voltjs.com gateway via
349
+ // VOLT_AI_TOKEN (free-capped, then pay-as-you-go on the host's key). The
350
+ // key/token never reaches the browser. ---
350
351
  if (req.method === "POST" && p === "/setup/ai") {
351
352
  let cbody = "";
352
353
  req.on("data", (c) => (cbody += c));
@@ -357,23 +358,30 @@ function startSetup() {
357
358
  const body = JSON.parse(cbody || "{}");
358
359
  const provider = body._provider || env.AI_PROVIDER || "anthropic";
359
360
  delete body._provider;
360
- let url, headers;
361
- if (provider === "anthropic") {
362
- if (!env.ANTHROPIC_API_KEY) throw new Error("ANTHROPIC_API_KEY not set in .env");
363
- url = "https://api.anthropic.com/v1/messages";
364
- headers = { "x-api-key": env.ANTHROPIC_API_KEY, "anthropic-version": "2023-06-01", "content-type": "application/json" };
365
- } else if (provider === "openai") {
366
- if (!env.OPENAI_API_KEY) throw new Error("OPENAI_API_KEY not set in .env");
367
- url = "https://api.openai.com/v1/chat/completions";
368
- headers = { authorization: "Bearer " + env.OPENAI_API_KEY, "content-type": "application/json" };
369
- } else if (provider === "gemini") {
370
- if (!env.GEMINI_API_KEY) throw new Error("GEMINI_API_KEY not set in .env");
371
- const model = body.model || "gemini-2.0-flash";
372
- delete body.model;
373
- url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${env.GEMINI_API_KEY}`;
374
- headers = { "content-type": "application/json" };
375
- } else throw new Error("unknown AI provider: " + provider);
376
- const r = await fetch(url, { method: "POST", headers, body: JSON.stringify(body) });
361
+ const localKey = { anthropic: env.ANTHROPIC_API_KEY, openai: env.OPENAI_API_KEY, gemini: env.GEMINI_API_KEY }[provider];
362
+ let url, headers, payload = body;
363
+ if (localKey) {
364
+ if (provider === "anthropic") {
365
+ url = "https://api.anthropic.com/v1/messages";
366
+ headers = { "x-api-key": localKey, "anthropic-version": "2023-06-01", "content-type": "application/json" };
367
+ } else if (provider === "openai") {
368
+ url = "https://api.openai.com/v1/chat/completions";
369
+ headers = { authorization: "Bearer " + localKey, "content-type": "application/json" };
370
+ } else if (provider === "gemini") {
371
+ const model = body.model || "gemini-2.0-flash";
372
+ delete body.model;
373
+ url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${localKey}`;
374
+ headers = { "content-type": "application/json" };
375
+ } else throw new Error("unknown AI provider: " + provider);
376
+ } else if (env.VOLT_AI_TOKEN) {
377
+ // no local key host gateway (free-capped, then pay-as-you-go)
378
+ url = env.VOLT_AI_GATEWAY || "https://voltjs.com/api/ai";
379
+ headers = { authorization: "Bearer " + env.VOLT_AI_TOKEN, "content-type": "application/json" };
380
+ payload = { messages: body.messages, system: body.system, max_tokens: body.max_tokens, model: body.model };
381
+ } else {
382
+ throw new Error("No AI key in .env and no VOLT_AI_TOKEN — add a provider key, or a gateway token to use the hosted tier.");
383
+ }
384
+ const r = await fetch(url, { method: "POST", headers, body: JSON.stringify(payload) });
377
385
  const text = await r.text();
378
386
  res.statusCode = r.status;
379
387
  res.setHeader("Content-Type", r.headers.get("content-type") || "application/json");
@@ -370,9 +370,10 @@ function startSetup() {
370
370
  }
371
371
  return;
372
372
  }
373
- // --- AI proxy for the in-config editor (RTEPro). Injects the .env provider
374
- // key server-side (never reaches the browser). BYO keys; RTEPro POSTs a
375
- // provider-native body with a _provider field. ---
373
+ // --- AI proxy for the in-config editor (RTEPro). Uses a local provider key
374
+ // (BYO) when set; otherwise falls back to the voltjs.com gateway via
375
+ // VOLT_AI_TOKEN (free-capped, then pay-as-you-go on the host's key). The
376
+ // key/token never reaches the browser. ---
376
377
  if (req.method === "POST" && p === "/setup/ai") {
377
378
  let cbody = "";
378
379
  req.on("data", (c) => (cbody += c));
@@ -383,23 +384,30 @@ function startSetup() {
383
384
  const body = JSON.parse(cbody || "{}");
384
385
  const provider = body._provider || env.AI_PROVIDER || "anthropic";
385
386
  delete body._provider;
386
- let url, headers;
387
- if (provider === "anthropic") {
388
- if (!env.ANTHROPIC_API_KEY) throw new Error("ANTHROPIC_API_KEY not set in .env");
389
- url = "https://api.anthropic.com/v1/messages";
390
- headers = { "x-api-key": env.ANTHROPIC_API_KEY, "anthropic-version": "2023-06-01", "content-type": "application/json" };
391
- } else if (provider === "openai") {
392
- if (!env.OPENAI_API_KEY) throw new Error("OPENAI_API_KEY not set in .env");
393
- url = "https://api.openai.com/v1/chat/completions";
394
- headers = { authorization: "Bearer " + env.OPENAI_API_KEY, "content-type": "application/json" };
395
- } else if (provider === "gemini") {
396
- if (!env.GEMINI_API_KEY) throw new Error("GEMINI_API_KEY not set in .env");
397
- const model = body.model || "gemini-2.0-flash";
398
- delete body.model;
399
- url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${env.GEMINI_API_KEY}`;
400
- headers = { "content-type": "application/json" };
401
- } else throw new Error("unknown AI provider: " + provider);
402
- const r = await fetch(url, { method: "POST", headers, body: JSON.stringify(body) });
387
+ const localKey = { anthropic: env.ANTHROPIC_API_KEY, openai: env.OPENAI_API_KEY, gemini: env.GEMINI_API_KEY }[provider];
388
+ let url, headers, payload = body;
389
+ if (localKey) {
390
+ if (provider === "anthropic") {
391
+ url = "https://api.anthropic.com/v1/messages";
392
+ headers = { "x-api-key": localKey, "anthropic-version": "2023-06-01", "content-type": "application/json" };
393
+ } else if (provider === "openai") {
394
+ url = "https://api.openai.com/v1/chat/completions";
395
+ headers = { authorization: "Bearer " + localKey, "content-type": "application/json" };
396
+ } else if (provider === "gemini") {
397
+ const model = body.model || "gemini-2.0-flash";
398
+ delete body.model;
399
+ url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${localKey}`;
400
+ headers = { "content-type": "application/json" };
401
+ } else throw new Error("unknown AI provider: " + provider);
402
+ } else if (env.VOLT_AI_TOKEN) {
403
+ // no local key host gateway (free-capped, then pay-as-you-go)
404
+ url = env.VOLT_AI_GATEWAY || "https://voltjs.com/api/ai";
405
+ headers = { authorization: "Bearer " + env.VOLT_AI_TOKEN, "content-type": "application/json" };
406
+ payload = { messages: body.messages, system: body.system, max_tokens: body.max_tokens, model: body.model };
407
+ } else {
408
+ throw new Error("No AI key in .env and no VOLT_AI_TOKEN — add a provider key, or a gateway token to use the hosted tier.");
409
+ }
410
+ const r = await fetch(url, { method: "POST", headers, body: JSON.stringify(payload) });
403
411
  const text = await r.text();
404
412
  res.statusCode = r.status;
405
413
  res.setHeader("Content-Type", r.headers.get("content-type") || "application/json");