viagen 0.0.10 → 0.0.12

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # viagen
2
2
 
3
- A dev server plugin (Vite, webpack, Next.js) and CLI tool that enables you to use Claude Code in a sandbox — instantly.
3
+ A Vite dev server plugin and CLI tool that enables you to use Claude Code in a sandbox — instantly.
4
4
 
5
5
  ## Prerequisites
6
6
 
@@ -14,8 +14,6 @@ A dev server plugin (Vite, webpack, Next.js) and CLI tool that enables you to us
14
14
  npm install --save-dev viagen
15
15
  ```
16
16
 
17
- ### Vite
18
-
19
17
  ```ts
20
18
  // vite.config.ts
21
19
  import { defineConfig } from 'vite'
@@ -26,66 +24,6 @@ export default defineConfig({
26
24
  })
27
25
  ```
28
26
 
29
- ### webpack
30
-
31
- ```js
32
- // webpack.config.js
33
- const { setupViagen } = require('viagen/webpack')
34
-
35
- module.exports = {
36
- devServer: {
37
- setupMiddlewares: (middlewares, devServer) => {
38
- setupViagen(devServer)
39
- return middlewares
40
- }
41
- }
42
- }
43
- ```
44
-
45
- ### Next.js
46
-
47
- Next.js uses its own dev server, so viagen runs via a custom server file.
48
-
49
- ```bash
50
- npm install --save-dev viagen connect
51
- ```
52
-
53
- ```ts
54
- // server.ts
55
- import http from 'node:http'
56
- import next from 'next'
57
- import connect from 'connect'
58
- import { setupViagen } from 'viagen/webpack'
59
-
60
- const dev = process.env.NODE_ENV !== 'production'
61
- const app = next({ dev, hostname: 'localhost', port: 3000 })
62
- const handle = app.getRequestHandler()
63
-
64
- app.prepare().then(() => {
65
- const server = connect()
66
-
67
- setupViagen({
68
- app: server,
69
- compiler: { hooks: { done: { tap: () => {} } } },
70
- })
71
-
72
- server.use((req, res) => handle(req, res))
73
- http.createServer(server).listen(3000, () => {
74
- console.log('> Ready on http://localhost:3000')
75
- })
76
- })
77
- ```
78
-
79
- Then update your dev script:
80
-
81
- ```json
82
- {
83
- "scripts": {
84
- "dev": "npx tsx server.ts"
85
- }
86
- }
87
- ```
88
-
89
27
  ## Step 2 — Setup
90
28
 
91
29
  ```bash
@@ -115,7 +53,7 @@ npx viagen sandbox --timeout 60
115
53
  npx viagen sandbox stop <sandboxId>
116
54
  ```
117
55
 
118
- ## Vite Plugin Options
56
+ ## Plugin Options
119
57
 
120
58
  ```ts
121
59
  viagen({
@@ -136,8 +74,17 @@ help build and modify the app. Files you edit will trigger Vite HMR
136
74
  automatically. You can read .viagen/server.log to check recent Vite dev server
137
75
  output (compile errors, HMR updates, warnings). When running in a sandbox with
138
76
  git, the gh CLI is available and authenticated — you can create pull requests,
139
- comment on issues, and manage releases. If Vercel credentials are set, you can
140
- run "vercel deploy" to publish a preview and share the URL. Be concise.
77
+ comment on issues, and manage releases.
78
+
79
+ Publishing workflow:
80
+ - If you are on a feature branch (not main/master): commit your changes, push
81
+ to the remote, and create a pull request using "gh pr create". Share the PR URL.
82
+ - If you are on main/master and Vercel credentials are set ($VERCEL_TOKEN):
83
+ commit, push, and run "vercel deploy" to publish a preview. Share the preview URL.
84
+ - Check your current branch with "git branch --show-current" before deciding
85
+ which workflow to use.
86
+
87
+ Be concise.
141
88
  ```
142
89
 
143
90
  Recent build errors are automatically appended to give Claude context about what went wrong. To customize the prompt, you can replace it entirely or extend the default:
@@ -154,18 +101,6 @@ viagen({
154
101
  })
155
102
  ```
156
103
 
157
- ## webpack Options
158
-
159
- ```js
160
- setupViagen(devServer, {
161
- position: 'bottom-right', // toggle button position
162
- model: 'sonnet', // claude model
163
- panelWidth: 420, // chat panel width in px
164
- ui: true, // inject chat panel into pages
165
- systemPrompt: '...', // custom system prompt
166
- })
167
- ```
168
-
169
104
  ## API
170
105
 
171
106
  Every viagen endpoint is available as an API. Build your own UI, integrate with CI, or script Claude from the command line.
package/dist/cli.js CHANGED
@@ -48,20 +48,39 @@ async function deploySandbox(opts) {
48
48
  const token = randomUUID();
49
49
  const useGit = !!opts.git;
50
50
  const timeoutMs = (opts.timeoutMinutes ?? 30) * 60 * 1e3;
51
- const sandbox2 = await Sandbox.create({
52
- runtime: "node22",
53
- ports: [5173],
54
- timeout: timeoutMs,
55
- ...opts.git ? {
56
- source: {
57
- type: "git",
58
- url: opts.git.remoteUrl,
59
- username: "x-access-token",
60
- password: opts.git.token,
61
- revision: opts.git.branch
62
- }
63
- } : {}
64
- });
51
+ const sourceOpts = opts.git ? {
52
+ source: {
53
+ type: "git",
54
+ url: opts.git.remoteUrl,
55
+ username: "x-access-token",
56
+ password: opts.git.token,
57
+ ...opts.git.revision ? { revision: opts.git.revision } : {}
58
+ }
59
+ } : {};
60
+ let sandbox2;
61
+ try {
62
+ sandbox2 = await Sandbox.create({
63
+ runtime: "node22",
64
+ ports: [5173],
65
+ timeout: timeoutMs,
66
+ ...sourceOpts
67
+ });
68
+ } catch (err) {
69
+ console.error("\nSandbox creation failed.");
70
+ if (opts.git) {
71
+ console.error(` URL: ${opts.git.remoteUrl}`);
72
+ console.error(` Revision: ${opts.git.revision ?? "(default branch)"}`);
73
+ console.error(` Branch: ${opts.git.branch}`);
74
+ }
75
+ const apiErr = err;
76
+ if (apiErr.message) console.error(` Message: ${apiErr.message}`);
77
+ if (apiErr.json) {
78
+ console.error(` Response: ${JSON.stringify(apiErr.json, null, 2)}`);
79
+ } else if (apiErr.text) {
80
+ console.error(` Response: ${apiErr.text}`);
81
+ }
82
+ throw err;
83
+ }
65
84
  try {
66
85
  if (useGit && opts.git) {
67
86
  await sandbox2.runCommand("git", [
@@ -108,26 +127,27 @@ async function deploySandbox(opts) {
108
127
  await sandbox2.writeFiles(files);
109
128
  }
110
129
  }
111
- const envLines = [
112
- `VIAGEN_AUTH_TOKEN=${token}`,
113
- `VIAGEN_SESSION_START=${Math.floor(Date.now() / 1e3)}`,
114
- `VIAGEN_SESSION_TIMEOUT=${(opts.timeoutMinutes ?? 30) * 60}`
115
- ];
130
+ const envMap = { ...opts.envVars ?? {} };
131
+ envMap["VIAGEN_AUTH_TOKEN"] = token;
132
+ envMap["VIAGEN_SESSION_START"] = String(Math.floor(Date.now() / 1e3));
133
+ envMap["VIAGEN_SESSION_TIMEOUT"] = String((opts.timeoutMinutes ?? 30) * 60);
116
134
  if (opts.apiKey) {
117
- envLines.push(`ANTHROPIC_API_KEY=${opts.apiKey}`);
135
+ envMap["ANTHROPIC_API_KEY"] = opts.apiKey;
118
136
  } else if (opts.oauth) {
119
- envLines.push(`CLAUDE_ACCESS_TOKEN=${opts.oauth.accessToken}`);
120
- envLines.push(`CLAUDE_REFRESH_TOKEN=${opts.oauth.refreshToken}`);
121
- envLines.push(`CLAUDE_TOKEN_EXPIRES=${opts.oauth.tokenExpires}`);
137
+ envMap["CLAUDE_ACCESS_TOKEN"] = opts.oauth.accessToken;
138
+ envMap["CLAUDE_REFRESH_TOKEN"] = opts.oauth.refreshToken;
139
+ envMap["CLAUDE_TOKEN_EXPIRES"] = opts.oauth.tokenExpires;
122
140
  }
123
141
  if (opts.git) {
124
- envLines.push(`GITHUB_TOKEN=${opts.git.token}`);
142
+ envMap["GITHUB_TOKEN"] = opts.git.token;
143
+ envMap["VIAGEN_BRANCH"] = opts.git.branch;
125
144
  }
126
145
  if (opts.vercel) {
127
- envLines.push(`VERCEL_TOKEN=${opts.vercel.token}`);
128
- envLines.push(`VERCEL_ORG_ID=${opts.vercel.teamId}`);
129
- envLines.push(`VERCEL_PROJECT_ID=${opts.vercel.projectId}`);
146
+ envMap["VERCEL_TOKEN"] = opts.vercel.token;
147
+ envMap["VERCEL_ORG_ID"] = opts.vercel.teamId;
148
+ envMap["VERCEL_PROJECT_ID"] = opts.vercel.projectId;
130
149
  }
150
+ const envLines = Object.entries(envMap).map(([k, v]) => `${k}=${v}`);
131
151
  await sandbox2.writeFiles([
132
152
  {
133
153
  path: ".env",
@@ -297,6 +317,22 @@ async function oauthConsoleFlow() {
297
317
  const keyData = await keyRes.json();
298
318
  return keyData.raw_key;
299
319
  }
320
+ async function refreshAccessToken(refresh) {
321
+ const res = await fetch(TOKEN_ENDPOINT, {
322
+ method: "POST",
323
+ headers: { "Content-Type": "application/json" },
324
+ body: JSON.stringify({
325
+ grant_type: "refresh_token",
326
+ client_id: CLIENT_ID,
327
+ refresh_token: refresh
328
+ })
329
+ });
330
+ if (!res.ok) {
331
+ const text = await res.text();
332
+ throw new Error(`Token refresh failed (${res.status}): ${text}`);
333
+ }
334
+ return await res.json();
335
+ }
300
336
 
301
337
  // src/cli.ts
302
338
  function loadDotenv(dir) {
@@ -334,6 +370,18 @@ function writeEnvVars(dir, vars) {
334
370
  content += toAdd.join("\n") + "\n";
335
371
  writeFileSync(envPath, content);
336
372
  }
373
+ function updateEnvVars(dir, vars) {
374
+ const envPath = join2(dir, ".env");
375
+ if (!existsSync(envPath)) return;
376
+ let content = readFileSync2(envPath, "utf-8");
377
+ for (const [key, val] of Object.entries(vars)) {
378
+ const re = new RegExp(`^${key}=.*$`, "m");
379
+ if (re.test(content)) {
380
+ content = content.replace(re, `${key}=${val}`);
381
+ }
382
+ }
383
+ writeFileSync(envPath, content);
384
+ }
337
385
  function openBrowser2(url) {
338
386
  try {
339
387
  const platform = process.platform;
@@ -384,6 +432,42 @@ function getGitInfo(cwd) {
384
432
  return null;
385
433
  }
386
434
  }
435
+ function remoteBranchExists(remoteUrl, branch, token) {
436
+ try {
437
+ const url = new URL(remoteUrl);
438
+ url.username = "x-access-token";
439
+ url.password = token;
440
+ const out = execSync2(
441
+ `git ls-remote --heads ${url.toString()} refs/heads/${branch}`,
442
+ { encoding: "utf-8", stdio: "pipe" }
443
+ ).trim();
444
+ return out.length > 0;
445
+ } catch {
446
+ return false;
447
+ }
448
+ }
449
+ function repoNwo(remoteUrl) {
450
+ const m = remoteUrl.match(/github\.com[/:]([^/]+\/[^/.]+?)(?:\.git)?$/);
451
+ return m ? m[1] : null;
452
+ }
453
+ function createRemoteBranch(remoteUrl, branch, token) {
454
+ const nwo = repoNwo(remoteUrl);
455
+ if (!nwo) return false;
456
+ try {
457
+ const sha = execSync2(
458
+ `gh api repos/${nwo}/git/ref/heads/main --jq .object.sha`,
459
+ { encoding: "utf-8", stdio: "pipe", env: { ...process.env, GH_TOKEN: token } }
460
+ ).trim();
461
+ if (!sha) return false;
462
+ execSync2(
463
+ `gh api repos/${nwo}/git/refs -f ref=refs/heads/${branch} -f sha=${sha}`,
464
+ { encoding: "utf-8", stdio: "pipe", env: { ...process.env, GH_TOKEN: token } }
465
+ );
466
+ return true;
467
+ } catch {
468
+ return false;
469
+ }
470
+ }
387
471
  function promptUser(question) {
388
472
  if (!process.stdin.isTTY) return Promise.resolve("");
389
473
  const rl = createInterface({ input: process.stdin, output: process.stdout });
@@ -650,6 +734,31 @@ async function sandbox(args) {
650
734
  );
651
735
  process.exit(1);
652
736
  }
737
+ if (hasOAuth && env["CLAUDE_REFRESH_TOKEN"]) {
738
+ const expires = parseInt(env["CLAUDE_TOKEN_EXPIRES"] || "0", 10);
739
+ const nowSec = Math.floor(Date.now() / 1e3);
740
+ if (nowSec > expires - 300) {
741
+ console.log("Refreshing Claude OAuth tokens...");
742
+ try {
743
+ const tokens = await refreshAccessToken(env["CLAUDE_REFRESH_TOKEN"]);
744
+ env["CLAUDE_ACCESS_TOKEN"] = tokens.access_token;
745
+ env["CLAUDE_REFRESH_TOKEN"] = tokens.refresh_token;
746
+ env["CLAUDE_TOKEN_EXPIRES"] = String(nowSec + tokens.expires_in);
747
+ updateEnvVars(cwd, {
748
+ CLAUDE_ACCESS_TOKEN: tokens.access_token,
749
+ CLAUDE_REFRESH_TOKEN: tokens.refresh_token,
750
+ CLAUDE_TOKEN_EXPIRES: String(nowSec + tokens.expires_in)
751
+ });
752
+ console.log(" Tokens refreshed.");
753
+ } catch (err) {
754
+ console.error(
755
+ "Failed to refresh OAuth tokens. Run `npx viagen setup` to re-authenticate."
756
+ );
757
+ console.error(` ${err instanceof Error ? err.message : String(err)}`);
758
+ process.exit(1);
759
+ }
760
+ }
761
+ }
653
762
  const hasOidc = !!env["VERCEL_OIDC_TOKEN"];
654
763
  const hasToken = !!env["VERCEL_TOKEN"] && !!env["VERCEL_TEAM_ID"] && !!env["VERCEL_PROJECT_ID"];
655
764
  if (!hasOidc && !hasToken) {
@@ -664,9 +773,33 @@ async function sandbox(args) {
664
773
  let overlayFiles;
665
774
  const branch = branchOverride || (gitInfo ? gitInfo.branch : "main");
666
775
  if (gitInfo && githubToken) {
776
+ let branchExistsOnRemote = remoteBranchExists(
777
+ gitInfo.remoteUrl,
778
+ branch,
779
+ githubToken
780
+ );
781
+ if (!branchExistsOnRemote) {
782
+ console.log(
783
+ `Branch "${branch}" not found on remote \u2014 creating it...`
784
+ );
785
+ const created = createRemoteBranch(
786
+ gitInfo.remoteUrl,
787
+ branch,
788
+ githubToken
789
+ );
790
+ if (created) {
791
+ console.log(` Branch "${branch}" created on remote (from main).`);
792
+ branchExistsOnRemote = true;
793
+ } else {
794
+ console.log(
795
+ ` Could not create branch on remote \u2014 will clone default branch and create locally.`
796
+ );
797
+ }
798
+ }
667
799
  const makeGitInfo = () => ({
668
800
  remoteUrl: gitInfo.remoteUrl,
669
801
  branch,
802
+ revision: branchExistsOnRemote ? branch : void 0,
670
803
  userName: gitInfo.userName,
671
804
  userEmail: gitInfo.userEmail,
672
805
  token: githubToken
@@ -718,6 +851,7 @@ async function sandbox(args) {
718
851
  } : void 0,
719
852
  git: deployGit,
720
853
  overlayFiles,
854
+ envVars: dotenv,
721
855
  vercel: env["VERCEL_TOKEN"] && env["VERCEL_TEAM_ID"] && env["VERCEL_PROJECT_ID"] ? {
722
856
  token: env["VERCEL_TOKEN"],
723
857
  teamId: env["VERCEL_TEAM_ID"],
package/dist/index.d.ts CHANGED
@@ -1,12 +1,14 @@
1
1
  import { Plugin } from 'vite';
2
2
 
3
- declare const DEFAULT_SYSTEM_PROMPT = "You are embedded in a Vite dev server as the \"viagen\" plugin. Your job is to help build and modify the app. Files you edit will trigger Vite HMR automatically. You can read .viagen/server.log to check recent Vite dev server output (compile errors, HMR updates, warnings). When running in a sandbox with git, the gh CLI is available and authenticated \u2014 you can create pull requests, comment on issues, and manage releases. If Vercel credentials are set, you can run \"vercel deploy\" to publish a preview and share the URL. Be concise.";
3
+ declare const DEFAULT_SYSTEM_PROMPT = "You are embedded in a Vite dev server as the \"viagen\" plugin. Your job is to help build and modify the app. Files you edit will trigger Vite HMR automatically. You can read .viagen/server.log to check recent Vite dev server output (compile errors, HMR updates, warnings). When running in a sandbox with git, the gh CLI is available and authenticated \u2014 you can create pull requests, comment on issues, and manage releases.\n\nPublishing workflow:\n- If you are on a feature branch (not main/master): commit your changes, push to the remote, and create a pull request using \"gh pr create\". Share the PR URL.\n- If you are on main/master and Vercel credentials are set ($VERCEL_TOKEN): commit, push, and run \"vercel deploy\" to publish a preview. Share the preview URL.\n- Check your current branch with \"git branch --show-current\" before deciding which workflow to use.\n\nBe concise.";
4
4
 
5
5
  interface GitInfo {
6
6
  /** HTTPS remote URL (transformed from SSH if needed). */
7
7
  remoteUrl: string;
8
8
  /** Branch to check out. */
9
9
  branch: string;
10
+ /** Revision to clone (branch/tag/sha). If omitted, clones the default branch. */
11
+ revision?: string;
10
12
  /** Git user name for commits. */
11
13
  userName: string;
12
14
  /** Git user email for commits. */
@@ -40,6 +42,8 @@ interface DeploySandboxOptions {
40
42
  };
41
43
  /** Sandbox timeout in minutes (default: 30, max depends on Vercel plan). */
42
44
  timeoutMinutes?: number;
45
+ /** User's .env variables to forward into the sandbox. */
46
+ envVars?: Record<string, string>;
43
47
  }
44
48
  interface DeploySandboxResult {
45
49
  /** Full URL with auth token in query string. */
package/dist/index.js CHANGED
@@ -1,11 +1,7 @@
1
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
- }) : x)(function(x) {
4
- if (typeof require !== "undefined") return require.apply(this, arguments);
5
- throw Error('Dynamic require of "' + x + '" is not supported');
6
- });
7
-
8
1
  // src/index.ts
2
+ import { readFileSync as readFileSync2 } from "fs";
3
+ import { dirname, join as join3 } from "path";
4
+ import { fileURLToPath } from "url";
9
5
  import { loadEnv } from "vite";
10
6
 
11
7
  // src/logger.ts
@@ -59,14 +55,15 @@ function wrapLogger(logger, buffer) {
59
55
  }
60
56
 
61
57
  // src/health.ts
62
- function registerHealthRoutes(app, env, errorRef) {
63
- app.use("/via/error", (_req, res) => {
58
+ function registerHealthRoutes(server, env, errorRef) {
59
+ server.middlewares.use("/via/error", (_req, res) => {
64
60
  res.setHeader("Content-Type", "application/json");
65
61
  res.end(JSON.stringify({ error: errorRef.get() }));
66
62
  });
67
- app.use("/via/health", (_req, res) => {
63
+ server.middlewares.use("/via/health", (_req, res) => {
68
64
  const configured = !!env["ANTHROPIC_API_KEY"] || !!env["CLAUDE_ACCESS_TOKEN"];
69
65
  const git = !!env["GITHUB_TOKEN"];
66
+ const branch = env["VIAGEN_BRANCH"] || null;
70
67
  const sessionStart = env["VIAGEN_SESSION_START"] ? parseInt(env["VIAGEN_SESSION_START"], 10) : null;
71
68
  const sessionTimeout = env["VIAGEN_SESSION_TIMEOUT"] ? parseInt(env["VIAGEN_SESSION_TIMEOUT"], 10) : null;
72
69
  const session = sessionStart && sessionTimeout ? {
@@ -76,10 +73,10 @@ function registerHealthRoutes(app, env, errorRef) {
76
73
  } : null;
77
74
  res.setHeader("Content-Type", "application/json");
78
75
  if (configured) {
79
- res.end(JSON.stringify({ status: "ok", configured: true, git, session }));
76
+ res.end(JSON.stringify({ status: "ok", configured: true, git, branch, session }));
80
77
  } else {
81
78
  res.end(
82
- JSON.stringify({ status: "error", configured: false, git, session })
79
+ JSON.stringify({ status: "error", configured: false, git, branch, session })
83
80
  );
84
81
  }
85
82
  });
@@ -123,20 +120,27 @@ function readBody(req) {
123
120
  req.on("error", reject);
124
121
  });
125
122
  }
126
- var DEFAULT_SYSTEM_PROMPT = `You are embedded in a Vite dev server as the "viagen" plugin. Your job is to help build and modify the app. Files you edit will trigger Vite HMR automatically. You can read .viagen/server.log to check recent Vite dev server output (compile errors, HMR updates, warnings). When running in a sandbox with git, the gh CLI is available and authenticated \u2014 you can create pull requests, comment on issues, and manage releases. If Vercel credentials are set, you can run "vercel deploy" to publish a preview and share the URL. Be concise.`;
123
+ var DEFAULT_SYSTEM_PROMPT = `You are embedded in a Vite dev server as the "viagen" plugin. Your job is to help build and modify the app. Files you edit will trigger Vite HMR automatically. You can read .viagen/server.log to check recent Vite dev server output (compile errors, HMR updates, warnings). When running in a sandbox with git, the gh CLI is available and authenticated \u2014 you can create pull requests, comment on issues, and manage releases.
124
+
125
+ Publishing workflow:
126
+ - If you are on a feature branch (not main/master): commit your changes, push to the remote, and create a pull request using "gh pr create". Share the PR URL.
127
+ - If you are on main/master and Vercel credentials are set ($VERCEL_TOKEN): commit, push, and run "vercel deploy" to publish a preview. Share the preview URL.
128
+ - Check your current branch with "git branch --show-current" before deciding which workflow to use.
129
+
130
+ Be concise.`;
127
131
  function findClaudeBin() {
128
- const _require = typeof __require !== "undefined" && typeof __require.resolve === "function" ? __require : createRequire(import.meta.url);
132
+ const _require = createRequire(import.meta.url);
129
133
  const pkgPath = _require.resolve("@anthropic-ai/claude-code/package.json");
130
134
  return pkgPath.replace("package.json", "cli.js");
131
135
  }
132
- function registerChatRoutes(app, opts) {
136
+ function registerChatRoutes(server, opts) {
133
137
  let sessionId;
134
- app.use("/via/chat/reset", (_req, res) => {
138
+ server.middlewares.use("/via/chat/reset", (_req, res) => {
135
139
  sessionId = void 0;
136
140
  res.setHeader("Content-Type", "application/json");
137
141
  res.end(JSON.stringify({ status: "ok" }));
138
142
  });
139
- app.use("/via/chat", async (req, res) => {
143
+ server.middlewares.use("/via/chat", async (req, res) => {
140
144
  if (req.method !== "POST") {
141
145
  res.statusCode = 405;
142
146
  res.end(JSON.stringify({ error: "Method not allowed" }));
@@ -321,7 +325,7 @@ function buildClientScript(opts) {
321
325
  /* js */
322
326
  `
323
327
  (function() {
324
- var OVERLAY_ENABLED = ${opts.overlay && (opts.buildTool ?? "vite") === "vite"};
328
+ var OVERLAY_ENABLED = ${opts.overlay};
325
329
  var EMBED_MODE = ${opts.embedMode ? "true" : "false"};
326
330
 
327
331
  /* ---- Error overlay: inject Fix button into shadow DOM ---- */
@@ -387,7 +391,7 @@ function buildClientScript(opts) {
387
391
  var errorData = await errorRes.json();
388
392
  if (!errorData.error) { win.classList.remove('viagen-fixing'); return; }
389
393
  var e = errorData.error;
390
- var prompt = 'Fix this build error in ' +
394
+ var prompt = 'Fix this Vite build error in ' +
391
395
  (e.loc ? e.loc.file + ':' + e.loc.line : 'unknown file') +
392
396
  ':\\n\\n' + e.message +
393
397
  (e.frame ? '\\n\\nCode frame:\\n' + e.frame : '');
@@ -1136,7 +1140,11 @@ function buildUiHtml() {
1136
1140
  });
1137
1141
  publishBtn.addEventListener('click', function () {
1138
1142
  if (isStreaming) return;
1139
- inputEl.value = 'Commit all changes, push to the remote repository, and run vercel deploy to get a preview URL';
1143
+ if (publishBtn.dataset.branch && publishBtn.dataset.branch !== 'main' && publishBtn.dataset.branch !== 'master') {
1144
+ inputEl.value = 'Commit all changes, push to the remote branch, and create a pull request using gh pr create. Share the PR URL.';
1145
+ } else {
1146
+ inputEl.value = 'Commit all changes, push to the remote repository, and run vercel deploy to get a preview URL';
1147
+ }
1140
1148
  send();
1141
1149
  });
1142
1150
 
@@ -1186,7 +1194,10 @@ function buildUiHtml() {
1186
1194
  var banner = document.getElementById('setup-banner');
1187
1195
  if (data.configured) {
1188
1196
  dot.className = 'status-dot ok';
1189
- if (data.git) publishBtn.style.display = '';
1197
+ if (data.git) {
1198
+ publishBtn.style.display = '';
1199
+ if (data.branch) publishBtn.dataset.branch = data.branch;
1200
+ }
1190
1201
  } else {
1191
1202
  dot.className = 'status-dot error';
1192
1203
  inputEl.disabled = true;
@@ -1276,7 +1287,7 @@ function createAuthMiddleware(token) {
1276
1287
  const cleanUrl = url.pathname + (url.search || "");
1277
1288
  res.setHeader(
1278
1289
  "Set-Cookie",
1279
- `viagen_session=${token}; HttpOnly; SameSite=Strict; Path=/`
1290
+ `viagen_session=${token}; HttpOnly; SameSite=Lax; Path=/; Secure`
1280
1291
  );
1281
1292
  res.writeHead(302, { Location: cleanUrl });
1282
1293
  res.end();
@@ -1329,20 +1340,39 @@ async function deploySandbox(opts) {
1329
1340
  const token = randomUUID();
1330
1341
  const useGit = !!opts.git;
1331
1342
  const timeoutMs = (opts.timeoutMinutes ?? 30) * 60 * 1e3;
1332
- const sandbox = await Sandbox.create({
1333
- runtime: "node22",
1334
- ports: [5173],
1335
- timeout: timeoutMs,
1336
- ...opts.git ? {
1337
- source: {
1338
- type: "git",
1339
- url: opts.git.remoteUrl,
1340
- username: "x-access-token",
1341
- password: opts.git.token,
1342
- revision: opts.git.branch
1343
- }
1344
- } : {}
1345
- });
1343
+ const sourceOpts = opts.git ? {
1344
+ source: {
1345
+ type: "git",
1346
+ url: opts.git.remoteUrl,
1347
+ username: "x-access-token",
1348
+ password: opts.git.token,
1349
+ ...opts.git.revision ? { revision: opts.git.revision } : {}
1350
+ }
1351
+ } : {};
1352
+ let sandbox;
1353
+ try {
1354
+ sandbox = await Sandbox.create({
1355
+ runtime: "node22",
1356
+ ports: [5173],
1357
+ timeout: timeoutMs,
1358
+ ...sourceOpts
1359
+ });
1360
+ } catch (err) {
1361
+ console.error("\nSandbox creation failed.");
1362
+ if (opts.git) {
1363
+ console.error(` URL: ${opts.git.remoteUrl}`);
1364
+ console.error(` Revision: ${opts.git.revision ?? "(default branch)"}`);
1365
+ console.error(` Branch: ${opts.git.branch}`);
1366
+ }
1367
+ const apiErr = err;
1368
+ if (apiErr.message) console.error(` Message: ${apiErr.message}`);
1369
+ if (apiErr.json) {
1370
+ console.error(` Response: ${JSON.stringify(apiErr.json, null, 2)}`);
1371
+ } else if (apiErr.text) {
1372
+ console.error(` Response: ${apiErr.text}`);
1373
+ }
1374
+ throw err;
1375
+ }
1346
1376
  try {
1347
1377
  if (useGit && opts.git) {
1348
1378
  await sandbox.runCommand("git", [
@@ -1389,26 +1419,27 @@ async function deploySandbox(opts) {
1389
1419
  await sandbox.writeFiles(files);
1390
1420
  }
1391
1421
  }
1392
- const envLines = [
1393
- `VIAGEN_AUTH_TOKEN=${token}`,
1394
- `VIAGEN_SESSION_START=${Math.floor(Date.now() / 1e3)}`,
1395
- `VIAGEN_SESSION_TIMEOUT=${(opts.timeoutMinutes ?? 30) * 60}`
1396
- ];
1422
+ const envMap = { ...opts.envVars ?? {} };
1423
+ envMap["VIAGEN_AUTH_TOKEN"] = token;
1424
+ envMap["VIAGEN_SESSION_START"] = String(Math.floor(Date.now() / 1e3));
1425
+ envMap["VIAGEN_SESSION_TIMEOUT"] = String((opts.timeoutMinutes ?? 30) * 60);
1397
1426
  if (opts.apiKey) {
1398
- envLines.push(`ANTHROPIC_API_KEY=${opts.apiKey}`);
1427
+ envMap["ANTHROPIC_API_KEY"] = opts.apiKey;
1399
1428
  } else if (opts.oauth) {
1400
- envLines.push(`CLAUDE_ACCESS_TOKEN=${opts.oauth.accessToken}`);
1401
- envLines.push(`CLAUDE_REFRESH_TOKEN=${opts.oauth.refreshToken}`);
1402
- envLines.push(`CLAUDE_TOKEN_EXPIRES=${opts.oauth.tokenExpires}`);
1429
+ envMap["CLAUDE_ACCESS_TOKEN"] = opts.oauth.accessToken;
1430
+ envMap["CLAUDE_REFRESH_TOKEN"] = opts.oauth.refreshToken;
1431
+ envMap["CLAUDE_TOKEN_EXPIRES"] = opts.oauth.tokenExpires;
1403
1432
  }
1404
1433
  if (opts.git) {
1405
- envLines.push(`GITHUB_TOKEN=${opts.git.token}`);
1434
+ envMap["GITHUB_TOKEN"] = opts.git.token;
1435
+ envMap["VIAGEN_BRANCH"] = opts.git.branch;
1406
1436
  }
1407
1437
  if (opts.vercel) {
1408
- envLines.push(`VERCEL_TOKEN=${opts.vercel.token}`);
1409
- envLines.push(`VERCEL_ORG_ID=${opts.vercel.teamId}`);
1410
- envLines.push(`VERCEL_PROJECT_ID=${opts.vercel.projectId}`);
1438
+ envMap["VERCEL_TOKEN"] = opts.vercel.token;
1439
+ envMap["VERCEL_ORG_ID"] = opts.vercel.teamId;
1440
+ envMap["VERCEL_PROJECT_ID"] = opts.vercel.projectId;
1411
1441
  }
1442
+ const envLines = Object.entries(envMap).map(([k, v]) => `${k}=${v}`);
1412
1443
  await sandbox.writeFiles([
1413
1444
  {
1414
1445
  path: ".env",
@@ -1443,6 +1474,14 @@ async function deploySandbox(opts) {
1443
1474
  }
1444
1475
 
1445
1476
  // src/index.ts
1477
+ var docsHtmlCache;
1478
+ function getDocsHtml() {
1479
+ if (!docsHtmlCache) {
1480
+ const dir = dirname(fileURLToPath(import.meta.url));
1481
+ docsHtmlCache = readFileSync2(join3(dir, "..", "site", "index.html"), "utf-8");
1482
+ }
1483
+ return docsHtmlCache;
1484
+ }
1446
1485
  function viagen(options) {
1447
1486
  const opts = {
1448
1487
  position: options?.position ?? "bottom-right",
@@ -1536,10 +1575,14 @@ ${payload.err.frame || ""}`
1536
1575
  res.setHeader("Content-Type", "text/html");
1537
1576
  res.end(buildIframeHtml({ panelWidth: opts.panelWidth }));
1538
1577
  });
1539
- registerHealthRoutes(server.middlewares, env, {
1578
+ server.middlewares.use("/via/docs", (_req, res) => {
1579
+ res.setHeader("Content-Type", "text/html");
1580
+ res.end(getDocsHtml());
1581
+ });
1582
+ registerHealthRoutes(server, env, {
1540
1583
  get: () => lastError
1541
1584
  });
1542
- registerChatRoutes(server.middlewares, {
1585
+ registerChatRoutes(server, {
1543
1586
  env,
1544
1587
  projectRoot,
1545
1588
  logBuffer,