viagen 0.0.56 → 0.1.2
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/cli.js +3 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +68 -33
- package/package.json +2 -2
- package/LICENSE +0 -21
- package/README.md +0 -170
package/dist/cli.js
CHANGED
|
@@ -252,7 +252,9 @@ async function deploySandbox(opts) {
|
|
|
252
252
|
sandboxId: sandbox2.sandboxId,
|
|
253
253
|
mode: useGit ? "git" : "upload",
|
|
254
254
|
streamLogs: (opts2) => devServer.logs(opts2),
|
|
255
|
-
stop: () =>
|
|
255
|
+
stop: async () => {
|
|
256
|
+
await sandbox2.stop();
|
|
257
|
+
}
|
|
256
258
|
};
|
|
257
259
|
} catch (err) {
|
|
258
260
|
await sandbox2.stop().catch(() => {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
|
+
import { McpServerConfig } from '@anthropic-ai/claude-agent-sdk';
|
|
2
3
|
|
|
3
4
|
declare const DEFAULT_SYSTEM_PROMPT = "\n You are embedded in a Vite dev server as the \"viagen\" plugin.\n Your job is to help build and modify the app. Files you edit will trigger Vite HMR automatically.\n You can read .viagen/server.log to check recent Vite dev server output (compile errors, HMR updates, warnings).\n Be concise.\n\n You have agent-browser available for interacting with the running app in a real browser.\n Use it to visually verify your changes, test interactions, and catch UI issues.\n Key commands:\n agent-browser open <url> \u2014 open a page\n agent-browser snapshot \u2014 get accessibility tree with element refs (@e1, @e2, etc.)\n agent-browser click @e<N> \u2014 click an element by ref\n agent-browser fill @e<N> \"value\" \u2014 fill an input\n agent-browser screenshot <file> \u2014 capture a screenshot (you can view the image)\n agent-browser diff snapshot --baseline <file> \u2014 compare page state before/after\n agent-browser close \u2014 close the browser\n After making changes, consider using agent-browser to verify the result visually.\n";
|
|
4
5
|
|
|
@@ -97,6 +98,8 @@ interface ViagenOptions {
|
|
|
97
98
|
* @example ['src/components', '.env', 'vite.config.ts']
|
|
98
99
|
*/
|
|
99
100
|
editable?: string[];
|
|
101
|
+
/** Additional MCP servers to make available to Claude. Merged with built-in viagen tools. */
|
|
102
|
+
mcpServers?: Record<string, McpServerConfig>;
|
|
100
103
|
/** Enable verbose debug logging. Also enabled by VIAGEN_DEBUG=1 in .env. */
|
|
101
104
|
debug?: boolean;
|
|
102
105
|
}
|
package/dist/index.js
CHANGED
|
@@ -101,16 +101,17 @@ function registerHealthRoutes(server, env, errorRef) {
|
|
|
101
101
|
const prompt = env["VIAGEN_PROMPT"] || null;
|
|
102
102
|
const taskId = env["VIAGEN_TASK_ID"] || null;
|
|
103
103
|
const environmentId = env["VIAGEN_ENVIRONMENT_ID"] || null;
|
|
104
|
+
const projectId = env["VIAGEN_PROJECT_ID"] || null;
|
|
104
105
|
res.setHeader("Content-Type", "application/json");
|
|
105
106
|
if (configured) {
|
|
106
|
-
res.end(JSON.stringify({ status: "ok", configured: true, git, vercel, branch, session, prompt, taskId, environmentId, missing }));
|
|
107
|
+
res.end(JSON.stringify({ status: "ok", configured: true, git, vercel, branch, session, prompt, taskId, environmentId, projectId, missing }));
|
|
107
108
|
} else {
|
|
108
109
|
res.end(
|
|
109
|
-
JSON.stringify({ status: "error", configured: false, git, vercel, branch, session, prompt, taskId, environmentId, missing })
|
|
110
|
+
JSON.stringify({ status: "error", configured: false, git, vercel, branch, session, prompt, taskId, environmentId, projectId, missing })
|
|
110
111
|
);
|
|
111
112
|
}
|
|
112
113
|
});
|
|
113
|
-
const currentVersion = true ? "0.
|
|
114
|
+
const currentVersion = true ? "0.1.2" : "0.0.0";
|
|
114
115
|
debug("health", `version resolved: ${currentVersion}`);
|
|
115
116
|
let versionCache = null;
|
|
116
117
|
server.middlewares.use("/via/version", (_req, res) => {
|
|
@@ -770,10 +771,10 @@ data: ${JSON.stringify(doneData)}
|
|
|
770
771
|
`);
|
|
771
772
|
res.end();
|
|
772
773
|
}
|
|
773
|
-
if (opts.viagenClient && opts.
|
|
774
|
+
if (opts.viagenClient && opts.projectId) {
|
|
774
775
|
const taskId = opts.env["VIAGEN_TASK_ID"];
|
|
775
776
|
if (taskId && (event.inputTokens || event.outputTokens)) {
|
|
776
|
-
opts.viagenClient.tasks.update(opts.
|
|
777
|
+
opts.viagenClient.tasks.update(opts.projectId, taskId, {
|
|
777
778
|
...event.inputTokens != null && { inputTokens: event.inputTokens },
|
|
778
779
|
...event.outputTokens != null && { outputTokens: event.outputTokens }
|
|
779
780
|
}).catch((err) => {
|
|
@@ -3798,7 +3799,7 @@ function buildUiHtml(opts) {
|
|
|
3798
3799
|
|
|
3799
3800
|
// Store task context for history rendering
|
|
3800
3801
|
healthTaskId = data.taskId || null;
|
|
3801
|
-
healthProjectId = data.environmentId || null;
|
|
3802
|
+
healthProjectId = data.projectId || data.environmentId || null;
|
|
3802
3803
|
|
|
3803
3804
|
// Load chat history from server (source of truth)
|
|
3804
3805
|
await loadHistory();
|
|
@@ -3892,7 +3893,7 @@ function buildUiHtml(opts) {
|
|
|
3892
3893
|
if (data.prompt && data.configured && chatLog.length === 0) {
|
|
3893
3894
|
if (data.taskId) {
|
|
3894
3895
|
// Task mode: show link instead of raw prompt
|
|
3895
|
-
var taskUrl = 'https://app.viagen.dev/
|
|
3896
|
+
var taskUrl = 'https://app.viagen.dev/projects/' + (data.projectId || data.environmentId) + '/tasks/' + data.taskId;
|
|
3896
3897
|
var div = document.createElement('div');
|
|
3897
3898
|
div.className = 'msg msg-user';
|
|
3898
3899
|
div.innerHTML = '<span class="label">Task</span><span class="text">Working on <a href="' + escapeHtml(taskUrl) + '" target="_blank" style="color:#2563eb;text-decoration:underline;">Via Task</a></span>';
|
|
@@ -4287,6 +4288,34 @@ function parseCookies(header) {
|
|
|
4287
4288
|
}
|
|
4288
4289
|
return cookies;
|
|
4289
4290
|
}
|
|
4291
|
+
function buildWaitPage(targetUrl) {
|
|
4292
|
+
return `<!DOCTYPE html>
|
|
4293
|
+
<html><head><meta charset="utf-8"><title>Loading\u2026</title>
|
|
4294
|
+
<style>
|
|
4295
|
+
body { margin: 0; display: flex; align-items: center; justify-content: center;
|
|
4296
|
+
height: 100vh; background: #0a0a0a; color: #888; font-family: system-ui; }
|
|
4297
|
+
.spinner { width: 20px; height: 20px; border: 2px solid #333; border-top-color: #f97316;
|
|
4298
|
+
border-radius: 50%; animation: spin .6s linear infinite; margin-right: 12px; }
|
|
4299
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
4300
|
+
</style></head>
|
|
4301
|
+
<body><div class="spinner"></div><span>Starting dev server\u2026</span>
|
|
4302
|
+
<script>
|
|
4303
|
+
(async () => {
|
|
4304
|
+
const target = ${JSON.stringify(targetUrl)};
|
|
4305
|
+
for (let i = 0; i < 60; i++) {
|
|
4306
|
+
try {
|
|
4307
|
+
const r = await fetch(target, { credentials: 'same-origin' });
|
|
4308
|
+
const text = await r.text();
|
|
4309
|
+
// Vite is ready when we get a non-empty HTML response
|
|
4310
|
+
if (r.ok && text.length > 0) { window.location.replace(target); return; }
|
|
4311
|
+
} catch {}
|
|
4312
|
+
await new Promise(r => setTimeout(r, 500));
|
|
4313
|
+
}
|
|
4314
|
+
// After 30s, navigate anyway \u2014 let the user see whatever error there is
|
|
4315
|
+
window.location.replace(target);
|
|
4316
|
+
})();
|
|
4317
|
+
</script></body></html>`;
|
|
4318
|
+
}
|
|
4290
4319
|
function createAuthMiddleware(token) {
|
|
4291
4320
|
return function authMiddleware(req, res, next) {
|
|
4292
4321
|
const url2 = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
@@ -4294,26 +4323,26 @@ function createAuthMiddleware(token) {
|
|
|
4294
4323
|
if (pathMatch && pathMatch[2] === token) {
|
|
4295
4324
|
const cleanPath = pathMatch[1] || "/";
|
|
4296
4325
|
const cleanUrl = cleanPath + (url2.search || "");
|
|
4297
|
-
debug("auth", `token URL match (/t/:token) \u2192 redirect to ${cleanUrl}`);
|
|
4326
|
+
debug("auth", `token URL match (/t/:token) \u2192 wait-page redirect to ${cleanUrl}`);
|
|
4298
4327
|
res.setHeader(
|
|
4299
4328
|
"Set-Cookie",
|
|
4300
4329
|
`viagen_session=${token}; HttpOnly; SameSite=Lax; Path=/`
|
|
4301
4330
|
);
|
|
4302
|
-
res.writeHead(
|
|
4303
|
-
res.end();
|
|
4331
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
4332
|
+
res.end(buildWaitPage(cleanUrl));
|
|
4304
4333
|
return;
|
|
4305
4334
|
}
|
|
4306
4335
|
const queryToken = url2.searchParams.get("token");
|
|
4307
4336
|
if (queryToken === token) {
|
|
4308
4337
|
url2.searchParams.delete("token");
|
|
4309
4338
|
const cleanUrl = url2.pathname + (url2.search || "");
|
|
4310
|
-
debug("auth", `query token match (?token=) \u2192 redirect to ${cleanUrl}`);
|
|
4339
|
+
debug("auth", `query token match (?token=) \u2192 wait-page redirect to ${cleanUrl}`);
|
|
4311
4340
|
res.setHeader(
|
|
4312
4341
|
"Set-Cookie",
|
|
4313
4342
|
`viagen_session=${token}; HttpOnly; SameSite=Lax; Path=/`
|
|
4314
4343
|
);
|
|
4315
|
-
res.writeHead(
|
|
4316
|
-
res.end();
|
|
4344
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
4345
|
+
res.end(buildWaitPage(cleanUrl));
|
|
4317
4346
|
return;
|
|
4318
4347
|
}
|
|
4319
4348
|
if (req.headers.cookie) {
|
|
@@ -18657,7 +18686,7 @@ import {
|
|
|
18657
18686
|
tool
|
|
18658
18687
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
18659
18688
|
function createViagenTools(config2) {
|
|
18660
|
-
const { client,
|
|
18689
|
+
const { client, projectId } = config2;
|
|
18661
18690
|
const taskId = process.env["VIAGEN_TASK_ID"];
|
|
18662
18691
|
const tools = [
|
|
18663
18692
|
tool(
|
|
@@ -18682,7 +18711,7 @@ function createViagenTools(config2) {
|
|
|
18682
18711
|
}
|
|
18683
18712
|
const internalStatus = args.status === "review" ? "validating" : args.status === "completed" ? "completed" : args.status;
|
|
18684
18713
|
try {
|
|
18685
|
-
await client.tasks.update(
|
|
18714
|
+
await client.tasks.update(projectId, id, {
|
|
18686
18715
|
...internalStatus && { status: internalStatus },
|
|
18687
18716
|
...args.prUrl && { prUrl: args.prUrl },
|
|
18688
18717
|
result: args.result,
|
|
@@ -18703,12 +18732,12 @@ function createViagenTools(config2) {
|
|
|
18703
18732
|
),
|
|
18704
18733
|
tool(
|
|
18705
18734
|
"viagen_list_tasks",
|
|
18706
|
-
"List tasks in the current
|
|
18735
|
+
"List tasks in the current project. Optionally filter by status.",
|
|
18707
18736
|
{
|
|
18708
18737
|
status: external_exports.enum(["ready", "running", "validating", "completed", "timed_out"]).optional().describe("Filter tasks by status.")
|
|
18709
18738
|
},
|
|
18710
18739
|
async (args) => {
|
|
18711
|
-
const tasks = await client.tasks.list(
|
|
18740
|
+
const tasks = await client.tasks.list(projectId, args.status);
|
|
18712
18741
|
return {
|
|
18713
18742
|
content: [
|
|
18714
18743
|
{
|
|
@@ -18726,7 +18755,7 @@ function createViagenTools(config2) {
|
|
|
18726
18755
|
taskId: external_exports.string().describe("The task ID to retrieve.")
|
|
18727
18756
|
},
|
|
18728
18757
|
async (args) => {
|
|
18729
|
-
const task = await client.tasks.get(
|
|
18758
|
+
const task = await client.tasks.get(projectId, args.taskId);
|
|
18730
18759
|
return {
|
|
18731
18760
|
content: [
|
|
18732
18761
|
{
|
|
@@ -18739,14 +18768,14 @@ function createViagenTools(config2) {
|
|
|
18739
18768
|
),
|
|
18740
18769
|
tool(
|
|
18741
18770
|
"viagen_create_task",
|
|
18742
|
-
"Create a new task in the current
|
|
18771
|
+
"Create a new task in the current project. Use this to create follow-up work.",
|
|
18743
18772
|
{
|
|
18744
18773
|
prompt: external_exports.string().describe("The task prompt / instructions."),
|
|
18745
18774
|
branch: external_exports.string().optional().describe("Git branch name for the task."),
|
|
18746
18775
|
type: external_exports.enum(["task", "plan"]).optional().describe("Task type: 'task' for code changes, 'plan' for implementation plans.")
|
|
18747
18776
|
},
|
|
18748
18777
|
async (args) => {
|
|
18749
|
-
const task = await client.tasks.create(
|
|
18778
|
+
const task = await client.tasks.create(projectId, {
|
|
18750
18779
|
prompt: args.prompt,
|
|
18751
18780
|
branch: args.branch,
|
|
18752
18781
|
type: args.type
|
|
@@ -18793,12 +18822,12 @@ Constraints:
|
|
|
18793
18822
|
`;
|
|
18794
18823
|
var TASK_TOOLS_PROMPT = `
|
|
18795
18824
|
You have access to viagen platform tools for task management:
|
|
18796
|
-
- viagen_list_tasks: List tasks in this
|
|
18825
|
+
- viagen_list_tasks: List tasks in this project (optionally filter by status)
|
|
18797
18826
|
- viagen_get_task: Get full details of a specific task
|
|
18798
18827
|
- viagen_create_task: Create follow-up tasks for work you identify
|
|
18799
18828
|
- viagen_update_task: Update a task's status ('review' or 'completed'). Accepts an optional taskId \u2014 defaults to the current task if one is set.
|
|
18800
18829
|
|
|
18801
|
-
Use these to understand
|
|
18830
|
+
Use these to understand project context and create follow-up work when appropriate.
|
|
18802
18831
|
`;
|
|
18803
18832
|
|
|
18804
18833
|
// src/index.ts
|
|
@@ -19049,7 +19078,9 @@ async function deploySandbox(opts) {
|
|
|
19049
19078
|
sandboxId: sandbox.sandboxId,
|
|
19050
19079
|
mode: useGit ? "git" : "upload",
|
|
19051
19080
|
streamLogs: (opts2) => devServer.logs(opts2),
|
|
19052
|
-
stop: () =>
|
|
19081
|
+
stop: async () => {
|
|
19082
|
+
await sandbox.stop();
|
|
19083
|
+
}
|
|
19053
19084
|
};
|
|
19054
19085
|
} catch (err) {
|
|
19055
19086
|
await sandbox.stop().catch(() => {
|
|
@@ -19182,7 +19213,7 @@ ${payload.err.frame || ""}`
|
|
|
19182
19213
|
}
|
|
19183
19214
|
const platformToken = env["VIAGEN_USER_TOKEN"] || env["VIAGEN_AUTH_TOKEN"];
|
|
19184
19215
|
const platformUrl = env["VIAGEN_PLATFORM_URL"] || "https://app.viagen.dev";
|
|
19185
|
-
const
|
|
19216
|
+
const projectId = env["VIAGEN_PROJECT_ID"];
|
|
19186
19217
|
let viagenClient = null;
|
|
19187
19218
|
if (platformToken) {
|
|
19188
19219
|
viagenClient = createViagen({ token: platformToken, baseUrl: platformUrl });
|
|
@@ -19222,9 +19253,9 @@ ${payload.err.frame || ""}`
|
|
|
19222
19253
|
res.end(JSON.stringify({ error: "Method not allowed" }));
|
|
19223
19254
|
return;
|
|
19224
19255
|
}
|
|
19225
|
-
if (!viagenClient || !
|
|
19256
|
+
if (!viagenClient || !projectId) {
|
|
19226
19257
|
res.writeHead(503, { "Content-Type": "application/json" });
|
|
19227
|
-
res.end(JSON.stringify({ error: "Task creation not configured: missing platform credentials or
|
|
19258
|
+
res.end(JSON.stringify({ error: "Task creation not configured: missing platform credentials or VIAGEN_PROJECT_ID" }));
|
|
19228
19259
|
return;
|
|
19229
19260
|
}
|
|
19230
19261
|
let body = "";
|
|
@@ -19246,7 +19277,7 @@ Page URL: ${pageUrl}`);
|
|
|
19246
19277
|
if (screenshot) parts.push("\n[Screenshot attached]");
|
|
19247
19278
|
parts.push("\n\n[Submitted via viagen preview feedback]");
|
|
19248
19279
|
const fullPrompt = parts.join("");
|
|
19249
|
-
const task = await viagenClient.tasks.create(
|
|
19280
|
+
const task = await viagenClient.tasks.create(projectId, { prompt: fullPrompt, type: "task" });
|
|
19250
19281
|
if (screenshot && task.id) {
|
|
19251
19282
|
try {
|
|
19252
19283
|
const [header, b64] = screenshot.split(",");
|
|
@@ -19254,7 +19285,7 @@ Page URL: ${pageUrl}`);
|
|
|
19254
19285
|
const binary = Buffer.from(b64, "base64");
|
|
19255
19286
|
const blob = new Blob([binary], { type: mime });
|
|
19256
19287
|
const ext = mime === "image/png" ? "png" : "jpeg";
|
|
19257
|
-
await viagenClient.tasks.addAttachment(
|
|
19288
|
+
await viagenClient.tasks.addAttachment(projectId, task.id, blob, `screenshot.${ext}`);
|
|
19258
19289
|
debug("preview", `screenshot attached to task ${task.id}`);
|
|
19259
19290
|
} catch (attachErr) {
|
|
19260
19291
|
debug("preview", `screenshot attachment failed (non-fatal): ${attachErr}`);
|
|
@@ -19277,15 +19308,19 @@ Page URL: ${pageUrl}`);
|
|
|
19277
19308
|
const resolvedModel = env["VIAGEN_MODEL"] || opts.model;
|
|
19278
19309
|
debug("server", `creating ChatSession (model: ${resolvedModel})`);
|
|
19279
19310
|
let mcpServers;
|
|
19280
|
-
const hasPlatformContext = !!(viagenClient &&
|
|
19311
|
+
const hasPlatformContext = !!(viagenClient && projectId);
|
|
19281
19312
|
if (hasPlatformContext) {
|
|
19282
19313
|
debug("server", "creating viagen MCP tools (platform connected)");
|
|
19283
19314
|
const viagenMcp = createViagenTools({
|
|
19284
19315
|
client: viagenClient,
|
|
19285
|
-
|
|
19316
|
+
projectId
|
|
19286
19317
|
});
|
|
19287
19318
|
mcpServers = { [viagenMcp.name]: viagenMcp };
|
|
19288
19319
|
}
|
|
19320
|
+
if (options?.mcpServers) {
|
|
19321
|
+
mcpServers = { ...mcpServers, ...options.mcpServers };
|
|
19322
|
+
debug("server", `merged ${Object.keys(options.mcpServers).length} user MCP server(s)`);
|
|
19323
|
+
}
|
|
19289
19324
|
const isPlanMode = env["VIAGEN_TASK_TYPE"] === "plan";
|
|
19290
19325
|
let systemPrompt = options?.systemPrompt;
|
|
19291
19326
|
if (isPlanMode) {
|
|
@@ -19310,7 +19345,7 @@ Page URL: ${pageUrl}`);
|
|
|
19310
19345
|
registerChatRoutes(server, chatSession, {
|
|
19311
19346
|
env,
|
|
19312
19347
|
viagenClient: viagenClient ?? void 0,
|
|
19313
|
-
|
|
19348
|
+
projectId
|
|
19314
19349
|
});
|
|
19315
19350
|
if (hasEditor) {
|
|
19316
19351
|
registerFileRoutes(server, {
|
|
@@ -19330,8 +19365,8 @@ Page URL: ${pageUrl}`);
|
|
|
19330
19365
|
debug("server", "auto-prompt completed");
|
|
19331
19366
|
logBuffer.push("info", `[viagen] Prompt completed`);
|
|
19332
19367
|
const currentTaskId = env["VIAGEN_TASK_ID"];
|
|
19333
|
-
if (viagenClient &&
|
|
19334
|
-
viagenClient.tasks.update(
|
|
19368
|
+
if (viagenClient && projectId && currentTaskId && (event.inputTokens || event.outputTokens)) {
|
|
19369
|
+
viagenClient.tasks.update(projectId, currentTaskId, {
|
|
19335
19370
|
...event.inputTokens != null && { inputTokens: event.inputTokens },
|
|
19336
19371
|
...event.outputTokens != null && { outputTokens: event.outputTokens }
|
|
19337
19372
|
}).catch((err) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "viagen",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Vite dev server plugin that exposes endpoints for chatting with Claude Code SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -50,6 +50,6 @@
|
|
|
50
50
|
"license": "MIT",
|
|
51
51
|
"repository": {
|
|
52
52
|
"type": "git",
|
|
53
|
-
"url": "https://github.com/viagen-dev/viagen"
|
|
53
|
+
"url": "https://github.com/viagen-dev/viagen-sdk"
|
|
54
54
|
}
|
|
55
55
|
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Ben Ipsen
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
package/README.md
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
# viagen
|
|
2
|
-
|
|
3
|
-
A Vite dev server plugin and CLI tool that enables you to use Claude Code in a sandbox — instantly.
|
|
4
|
-
|
|
5
|
-
## Prerequisites
|
|
6
|
-
|
|
7
|
-
- [Claude](https://claude.ai/signup) — Max, Pro, or API plan. The setup wizard handles auth.
|
|
8
|
-
- [Vercel](https://vercel.com/signup) — Free plan works. Sandboxes last 45 min on Hobby, 5 hours on Pro.
|
|
9
|
-
- [GitHub CLI](https://cli.github.com) — Enables git clone and push from sandboxes.
|
|
10
|
-
|
|
11
|
-
## Quick Setup (Claude Code Plugin)
|
|
12
|
-
|
|
13
|
-
```
|
|
14
|
-
/plugin marketplace add viagen-dev/viagen-claude-plugin
|
|
15
|
-
/plugin install viagen@viagen-marketplace
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
**Restart Claude Code to load the plugin.**
|
|
19
|
-
|
|
20
|
-
```
|
|
21
|
-
/viagen-install
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
The plugin will handle npm installation, vite config updates, and run the setup wizard for you.
|
|
25
|
-
|
|
26
|
-
## Manual Setup
|
|
27
|
-
|
|
28
|
-
### Step 1 — Add viagen to your app
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
npm install --save-dev viagen
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
```ts
|
|
35
|
-
// vite.config.ts
|
|
36
|
-
import { defineConfig } from 'vite'
|
|
37
|
-
import { viagen } from 'viagen'
|
|
38
|
-
|
|
39
|
-
export default defineConfig({
|
|
40
|
-
plugins: [viagen()],
|
|
41
|
-
})
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
### Step 2 — Setup
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
npx viagen setup
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
The setup wizard authenticates with Claude, detects your GitHub and Vercel credentials, and captures your git remote info — all written to your local `.env`. This ensures sandboxes clone the correct repo instead of inferring it at runtime.
|
|
51
|
-
|
|
52
|
-
You can now run `npm run dev` to start the local dev server. At this point you can launch viagen and chat with Claude to make changes to your app.
|
|
53
|
-
|
|
54
|
-
### Step 3 — Sandbox
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
npx viagen sandbox
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
Deploys your dev server to a remote Vercel Sandbox — an isolated VM-like environment where Claude can read, write, and push code.
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
# Deploy on a specific branch
|
|
64
|
-
npx viagen sandbox --branch feature/my-thing
|
|
65
|
-
|
|
66
|
-
# Set a longer timeout (default: 30 min)
|
|
67
|
-
npx viagen sandbox --timeout 60
|
|
68
|
-
|
|
69
|
-
# Auto-send a prompt on load
|
|
70
|
-
npx viagen sandbox --prompt "build me a landing page"
|
|
71
|
-
|
|
72
|
-
# Stop a running sandbox
|
|
73
|
-
npx viagen sandbox stop <sandboxId>
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
## Plugin Options
|
|
78
|
-
|
|
79
|
-
```ts
|
|
80
|
-
viagen({
|
|
81
|
-
position: 'bottom-right', // toggle button position
|
|
82
|
-
model: 'sonnet', // claude model
|
|
83
|
-
panelWidth: 375, // chat panel width in px
|
|
84
|
-
overlay: true, // fix button on error overlay
|
|
85
|
-
ui: true, // inject chat panel into pages
|
|
86
|
-
sandboxFiles: [...], // copy files manually into sandbox
|
|
87
|
-
systemPrompt: '...', // custom system prompt (see below)
|
|
88
|
-
editable: ['src','conf'], // files/dirs editable in the UI
|
|
89
|
-
})
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
### Editable Files
|
|
94
|
-
|
|
95
|
-
Add a file editor panel to the chat UI:
|
|
96
|
-
|
|
97
|
-
```ts
|
|
98
|
-
viagen({
|
|
99
|
-
editable: ['src/components', 'vite.config.ts']
|
|
100
|
-
})
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
Paths can be files or directories (directories include all files within). The editor appears as a "Files" tab in the chat panel with a collapsible directory tree, syntax highlighting (TypeScript, JavaScript, CSS, HTML, JSON, Markdown), and image preview.
|
|
104
|
-
|
|
105
|
-
The default system prompt tells Claude it's embedded in a Vite dev server, that file edits trigger HMR, and how to check server logs. Recent build errors are automatically appended to give Claude context about what went wrong.
|
|
106
|
-
|
|
107
|
-
To customize the prompt, you can replace it entirely or extend the default:
|
|
108
|
-
|
|
109
|
-
```ts
|
|
110
|
-
import { viagen, DEFAULT_SYSTEM_PROMPT } from 'viagen'
|
|
111
|
-
|
|
112
|
-
viagen({
|
|
113
|
-
// Replace entirely
|
|
114
|
-
systemPrompt: 'You are a React expert. Only use TypeScript.',
|
|
115
|
-
|
|
116
|
-
// Or extend the default
|
|
117
|
-
systemPrompt: DEFAULT_SYSTEM_PROMPT + '\nAlways use Tailwind for styling.',
|
|
118
|
-
})
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
## API
|
|
122
|
-
|
|
123
|
-
Every viagen endpoint is available as an API. Build your own UI, integrate with CI, or script Claude from the command line.
|
|
124
|
-
|
|
125
|
-
```
|
|
126
|
-
POST /via/chat — send a message, streamed SSE response
|
|
127
|
-
POST /via/chat/reset — clear conversation history
|
|
128
|
-
GET /via/health — check API key status
|
|
129
|
-
GET /via/error — latest build error (if any)
|
|
130
|
-
GET /via/ui — standalone chat interface
|
|
131
|
-
GET /via/iframe — split view (app + chat side by side)
|
|
132
|
-
GET /via/files — list editable files (when configured)
|
|
133
|
-
GET /via/file?path= — read file content
|
|
134
|
-
POST /via/file — write file content { path, content }
|
|
135
|
-
GET /via/file/raw — serve raw file (images, etc.) with correct MIME type
|
|
136
|
-
GET /via/git/status — list changed files (git status)
|
|
137
|
-
GET /via/git/diff — full diff, or single file with ?path=
|
|
138
|
-
GET /via/git/branch — current branch, remote URL, open PR info
|
|
139
|
-
GET /via/logs — dev server log entries, optional ?since=<timestamp>
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
When `VIAGEN_AUTH_TOKEN` is set (always on in sandboxes), pass the token as a `Bearer` header, a `/t/:token` path segment, or a `?token=` query param.
|
|
143
|
-
|
|
144
|
-
```bash
|
|
145
|
-
# With curl
|
|
146
|
-
curl -X POST http://localhost:5173/via/chat \
|
|
147
|
-
-H "Authorization: Bearer $VIAGEN_AUTH_TOKEN" \
|
|
148
|
-
-H "Content-Type: application/json" \
|
|
149
|
-
-d '{"message": "add a hello world route"}'
|
|
150
|
-
|
|
151
|
-
# Or pass the token in the URL path (sets a session cookie)
|
|
152
|
-
open "http://localhost:5173/via/ui/t/$VIAGEN_AUTH_TOKEN"
|
|
153
|
-
|
|
154
|
-
# ?token= query param also works (fallback for backwards compat)
|
|
155
|
-
open "http://localhost:5173/via/ui?token=$VIAGEN_AUTH_TOKEN"
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
## Development
|
|
159
|
-
|
|
160
|
-
```bash
|
|
161
|
-
npm install
|
|
162
|
-
npm run dev # Dev server (site)
|
|
163
|
-
npm run build # Build with tsup
|
|
164
|
-
npm run test # Run tests
|
|
165
|
-
npm run typecheck # Type check
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
## License
|
|
169
|
-
|
|
170
|
-
[MIT](LICENSE)
|