viagen 0.0.17 → 0.0.21

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
@@ -49,10 +49,25 @@ npx viagen sandbox --branch feature/my-thing
49
49
  # Set a longer timeout (default: 30 min)
50
50
  npx viagen sandbox --timeout 60
51
51
 
52
+ # Auto-send a prompt on load
53
+ npx viagen sandbox --prompt "build me a landing page"
54
+
52
55
  # Stop a running sandbox
53
56
  npx viagen sandbox stop <sandboxId>
54
57
  ```
55
58
 
59
+ ## Step 4 — Sync
60
+
61
+ ```bash
62
+ npx viagen sync
63
+ ```
64
+
65
+ Pushes your local `.env` credentials (Claude Max tokens, GitHub token, Vercel config) to a platform project. This lets you launch sandboxes from the web — use your Max subscription from any device without needing a browser redirect.
66
+
67
+ The first run prompts you to pick or create a project. After that, subsequent syncs target the same project automatically (stored as `VIAGEN_PROJECT_ID` in `.env`).
68
+
69
+ Claude tokens are refreshed automatically if expired before syncing. Requires `viagen login` first.
70
+
56
71
  ## Plugin Options
57
72
 
58
73
  ```ts
@@ -98,7 +113,7 @@ Add a file editor panel to the chat UI:
98
113
 
99
114
  ```ts
100
115
  viagen({
101
- editable: ['src/components', '.env', 'vite.config.ts']
116
+ editable: ['src/components', 'vite.config.ts']
102
117
  })
103
118
  ```
104
119
 
@@ -153,6 +168,9 @@ GET /via/iframe — split view (app + chat side by side)
153
168
  GET /via/files — list editable files (when configured)
154
169
  GET /via/file?path= — read file content
155
170
  POST /via/file — write file content { path, content }
171
+ GET /via/git/status — list changed files (git status)
172
+ GET /via/git/diff — full diff, or single file with ?path=
173
+ GET /via/logs — dev server log entries, optional ?since=<timestamp>
156
174
  ```
157
175
 
158
176
  When `VIAGEN_AUTH_TOKEN` is set (always on in sandboxes), pass the token as a `Bearer` header or `?token=` query param.
package/dist/cli.js CHANGED
@@ -147,6 +147,9 @@ async function deploySandbox(opts) {
147
147
  envMap["VERCEL_ORG_ID"] = opts.vercel.teamId;
148
148
  envMap["VERCEL_PROJECT_ID"] = opts.vercel.projectId;
149
149
  }
150
+ if (opts.prompt) {
151
+ envMap["VIAGEN_PROMPT"] = opts.prompt;
152
+ }
150
153
  const envLines = Object.entries(envMap).map(([k, v]) => `${k}=${v}`);
151
154
  await sandbox2.writeFiles([
152
155
  {
@@ -337,6 +340,7 @@ async function refreshAccessToken(refresh) {
337
340
  // src/cli.ts
338
341
  import {
339
342
  createViagen,
343
+ ViagenApiError,
340
344
  saveCredentials,
341
345
  loadCredentials,
342
346
  clearCredentials
@@ -828,6 +832,7 @@ async function sandbox(args) {
828
832
  const branchOverride = parseFlag(args, "--branch") || parseFlag(args, "-b");
829
833
  const timeoutFlag = parseFlag(args, "--timeout") || parseFlag(args, "-t");
830
834
  const timeoutMinutes = timeoutFlag ? parseInt(timeoutFlag, 10) : void 0;
835
+ const prompt = parseFlag(args, "--prompt") || parseFlag(args, "-p");
831
836
  const cwd = process.cwd();
832
837
  const dotenv = loadDotenv(cwd);
833
838
  for (const [key, val] of Object.entries(dotenv)) {
@@ -1002,7 +1007,8 @@ async function sandbox(args) {
1002
1007
  teamId: env["VERCEL_TEAM_ID"],
1003
1008
  projectId: env["VERCEL_PROJECT_ID"]
1004
1009
  } : void 0,
1005
- timeoutMinutes
1010
+ timeoutMinutes,
1011
+ prompt
1006
1012
  });
1007
1013
  const iframeUrl = result.url.replace("?token=", "/via/iframe?token=");
1008
1014
  const chatUrl = result.url.replace("?token=", "/via/ui?token=");
@@ -1022,7 +1028,164 @@ async function sandbox(args) {
1022
1028
  console.log(`Stop with: npx viagen sandbox stop ${result.sandboxId}`);
1023
1029
  openBrowser2(iframeUrl);
1024
1030
  }
1025
- var PLATFORM_URL = "http://localhost:5175";
1031
+ var SYNC_KEYS = [
1032
+ "CLAUDE_ACCESS_TOKEN",
1033
+ "CLAUDE_REFRESH_TOKEN",
1034
+ "CLAUDE_TOKEN_EXPIRES",
1035
+ "ANTHROPIC_API_KEY",
1036
+ "GITHUB_TOKEN",
1037
+ "VERCEL_TOKEN",
1038
+ "VERCEL_TEAM_ID",
1039
+ "VERCEL_PROJECT_ID",
1040
+ "GIT_USER_NAME",
1041
+ "GIT_USER_EMAIL"
1042
+ ];
1043
+ function gatherSyncSecrets(env) {
1044
+ const secrets = {};
1045
+ for (const key of SYNC_KEYS) {
1046
+ if (env[key]) secrets[key] = env[key];
1047
+ }
1048
+ return secrets;
1049
+ }
1050
+ async function sync() {
1051
+ const client = await requireClient();
1052
+ const cwd = process.cwd();
1053
+ const env = loadDotenv(cwd);
1054
+ console.log("viagen sync");
1055
+ console.log("");
1056
+ let projectId = env["VIAGEN_PROJECT_ID"];
1057
+ let projectName;
1058
+ if (projectId) {
1059
+ try {
1060
+ const existing = await client.projects.get(projectId);
1061
+ projectName = existing.name;
1062
+ console.log(`Project: ${projectName}`);
1063
+ } catch {
1064
+ console.log("Previously synced project not found. Choose a project:");
1065
+ console.log("");
1066
+ projectId = void 0;
1067
+ }
1068
+ }
1069
+ if (!projectId) {
1070
+ const projects2 = await client.projects.list();
1071
+ if (projects2.length > 0) {
1072
+ console.log("Your projects:");
1073
+ console.log("");
1074
+ for (let i = 0; i < projects2.length; i++) {
1075
+ const repo = projects2[i].githubRepo ? ` (${projects2[i].githubRepo})` : "";
1076
+ console.log(` ${i + 1}) ${projects2[i].name}${repo}`);
1077
+ }
1078
+ console.log(` ${projects2.length + 1}) Create new project`);
1079
+ console.log("");
1080
+ const choice = await promptUser("Choose: ");
1081
+ const idx = parseInt(choice, 10) - 1;
1082
+ if (idx >= 0 && idx < projects2.length) {
1083
+ projectId = projects2[idx].id;
1084
+ projectName = projects2[idx].name;
1085
+ } else {
1086
+ projectName = await promptUser("Project name: ");
1087
+ if (!projectName) {
1088
+ console.log("Cancelled.");
1089
+ return;
1090
+ }
1091
+ }
1092
+ } else {
1093
+ projectName = await promptUser("Project name: ");
1094
+ if (!projectName) {
1095
+ console.log("Cancelled.");
1096
+ return;
1097
+ }
1098
+ }
1099
+ }
1100
+ const secrets = gatherSyncSecrets(env);
1101
+ if (Object.keys(secrets).length === 0) {
1102
+ console.error("No credentials found in .env to sync.");
1103
+ console.error("Run `viagen setup` first to configure credentials.");
1104
+ process.exit(1);
1105
+ }
1106
+ if (secrets["CLAUDE_ACCESS_TOKEN"] && env["CLAUDE_REFRESH_TOKEN"]) {
1107
+ const expires = parseInt(env["CLAUDE_TOKEN_EXPIRES"] || "0", 10);
1108
+ const nowSec = Math.floor(Date.now() / 1e3);
1109
+ if (nowSec > expires - 300) {
1110
+ console.log("Refreshing Claude tokens...");
1111
+ try {
1112
+ const tokens = await refreshAccessToken(env["CLAUDE_REFRESH_TOKEN"]);
1113
+ secrets["CLAUDE_ACCESS_TOKEN"] = tokens.access_token;
1114
+ secrets["CLAUDE_REFRESH_TOKEN"] = tokens.refresh_token;
1115
+ secrets["CLAUDE_TOKEN_EXPIRES"] = String(nowSec + tokens.expires_in);
1116
+ updateEnvVars(cwd, {
1117
+ CLAUDE_ACCESS_TOKEN: tokens.access_token,
1118
+ CLAUDE_REFRESH_TOKEN: tokens.refresh_token,
1119
+ CLAUDE_TOKEN_EXPIRES: String(nowSec + tokens.expires_in)
1120
+ });
1121
+ console.log(" Tokens refreshed.");
1122
+ } catch (err) {
1123
+ console.error(
1124
+ "Failed to refresh Claude tokens. Run `viagen setup` to re-authenticate."
1125
+ );
1126
+ console.error(` ${err instanceof Error ? err.message : String(err)}`);
1127
+ process.exit(1);
1128
+ }
1129
+ }
1130
+ }
1131
+ console.log("");
1132
+ console.log(`Secrets to sync (${Object.keys(secrets).length}):`);
1133
+ for (const key of Object.keys(secrets)) {
1134
+ console.log(` ${key}`);
1135
+ }
1136
+ console.log("");
1137
+ const answer = await promptUser("Push to platform? [y/n]: ");
1138
+ if (answer !== "y" && answer !== "yes") {
1139
+ console.log("Cancelled.");
1140
+ return;
1141
+ }
1142
+ const gitRemote = env["GIT_REMOTE_URL"];
1143
+ const githubRepo = gitRemote ? repoNwo(gitRemote) : void 0;
1144
+ const gitInfo = getGitInfo(cwd);
1145
+ const gitBranch = env["GIT_BRANCH"] || gitInfo?.branch;
1146
+ console.log("");
1147
+ console.log("Syncing...");
1148
+ const syncInput = {
1149
+ ...projectId ? { id: projectId } : {},
1150
+ name: projectName,
1151
+ ...githubRepo ? { githubRepo } : {},
1152
+ ...gitBranch ? { gitBranch } : {},
1153
+ secrets
1154
+ };
1155
+ let result;
1156
+ try {
1157
+ result = await client.projects.sync(syncInput);
1158
+ } catch (err) {
1159
+ const logDir = join2(cwd, ".viagen");
1160
+ const logFile = join2(logDir, "sync-error.log");
1161
+ if (!existsSync(logDir)) {
1162
+ const { mkdirSync } = await import("fs");
1163
+ mkdirSync(logDir, { recursive: true });
1164
+ }
1165
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1166
+ const lines = [`[${timestamp}] viagen sync failed`];
1167
+ if (err instanceof ViagenApiError) {
1168
+ lines.push(`Status: ${err.status}`);
1169
+ lines.push(`Message: ${err.message}`);
1170
+ if (err.detail) lines.push(`Detail: ${err.detail}`);
1171
+ } else {
1172
+ lines.push(`Error: ${err instanceof Error ? err.stack || err.message : String(err)}`);
1173
+ }
1174
+ lines.push(`Input: ${JSON.stringify({ ...syncInput, secrets: Object.keys(syncInput.secrets || {}) })}`);
1175
+ writeFileSync(logFile, lines.join("\n") + "\n");
1176
+ console.error("Sync failed. A complete log has been written to:");
1177
+ console.error(` ${logFile}`);
1178
+ process.exit(1);
1179
+ }
1180
+ if (!env["VIAGEN_PROJECT_ID"]) {
1181
+ writeEnvVars(cwd, { VIAGEN_PROJECT_ID: result.project.id });
1182
+ }
1183
+ console.log(
1184
+ `Synced "${result.project.name}" \u2014 ${result.secrets.stored} secrets stored.`
1185
+ );
1186
+ console.log("You can now launch sandboxes from the web platform.");
1187
+ }
1188
+ var PLATFORM_URL = process.env.VIAGEN_PLATFORM_URL || "https://app.viagen.dev";
1026
1189
  async function login() {
1027
1190
  const existing = await loadCredentials();
1028
1191
  if (existing) {
@@ -1140,7 +1303,7 @@ async function orgs(args) {
1140
1303
  }
1141
1304
  for (const m of memberships) {
1142
1305
  const role = m.role ? ` (${m.role})` : "";
1143
- console.log(` ${m.organizationName}${role}`);
1306
+ console.log(` ${m.name}${role}`);
1144
1307
  }
1145
1308
  }
1146
1309
  async function projects(args) {
@@ -1216,6 +1379,7 @@ function help() {
1216
1379
  console.log(" setup Set up .env with API keys and tokens");
1217
1380
  console.log(" sandbox [-b branch] [-t min] Deploy your project to a Vercel Sandbox");
1218
1381
  console.log(" sandbox stop <id> Stop a running sandbox");
1382
+ console.log(" sync Push local credentials to the platform");
1219
1383
  console.log(" help Show this help message");
1220
1384
  console.log("");
1221
1385
  console.log("Sandbox options:");
@@ -1285,6 +1449,8 @@ async function main() {
1285
1449
  await setup();
1286
1450
  } else if (command === "sandbox") {
1287
1451
  await sandbox(args.slice(1));
1452
+ } else if (command === "sync") {
1453
+ await sync();
1288
1454
  } else {
1289
1455
  help();
1290
1456
  }
@@ -1293,3 +1459,6 @@ main().catch((err) => {
1293
1459
  console.error(err.message || err);
1294
1460
  process.exit(1);
1295
1461
  });
1462
+ export {
1463
+ gatherSyncSecrets
1464
+ };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
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.\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.";
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): First, ensure you are working in the git root directory. 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 --yes\" to publish a preview. Share the preview URL. If vercel is not configured correctly. Skip this step.\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). */
@@ -44,6 +44,8 @@ interface DeploySandboxOptions {
44
44
  timeoutMinutes?: number;
45
45
  /** User's .env variables to forward into the sandbox. */
46
46
  envVars?: Record<string, string>;
47
+ /** Initial prompt to auto-send in the chat UI on load. */
48
+ prompt?: string;
47
49
  }
48
50
  interface DeploySandboxResult {
49
51
  /** Full URL with auth token in query string. */