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 +19 -1
- package/dist/cli.js +172 -3
- package/dist/index.d.ts +3 -1
- package/dist/index.js +905 -204
- package/package.json +3 -2
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', '
|
|
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
|
|
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.
|
|
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):
|
|
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. */
|