viagen 0.0.53 → 0.0.55

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/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Plugin } from 'vite';
2
2
 
3
- 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";
3
+ 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
4
 
5
5
  interface GitInfo {
6
6
  /** HTTPS remote URL (transformed from SSH if needed). */
package/dist/index.js CHANGED
@@ -110,7 +110,7 @@ function registerHealthRoutes(server, env, errorRef) {
110
110
  );
111
111
  }
112
112
  });
113
- const currentVersion = true ? "0.0.53" : "0.0.0";
113
+ const currentVersion = true ? "0.0.55" : "0.0.0";
114
114
  debug("health", `version resolved: ${currentVersion}`);
115
115
  let versionCache = null;
116
116
  server.middlewares.use("/via/version", (_req, res) => {
@@ -241,6 +241,18 @@ var DEFAULT_SYSTEM_PROMPT = `
241
241
  Your job is to help build and modify the app. Files you edit will trigger Vite HMR automatically.
242
242
  You can read .viagen/server.log to check recent Vite dev server output (compile errors, HMR updates, warnings).
243
243
  Be concise.
244
+
245
+ You have agent-browser available for interacting with the running app in a real browser.
246
+ Use it to visually verify your changes, test interactions, and catch UI issues.
247
+ Key commands:
248
+ agent-browser open <url> \u2014 open a page
249
+ agent-browser snapshot \u2014 get accessibility tree with element refs (@e1, @e2, etc.)
250
+ agent-browser click @e<N> \u2014 click an element by ref
251
+ agent-browser fill @e<N> "value" \u2014 fill an input
252
+ agent-browser screenshot <file> \u2014 capture a screenshot (you can view the image)
253
+ agent-browser diff snapshot --baseline <file> \u2014 compare page state before/after
254
+ agent-browser close \u2014 close the browser
255
+ After making changes, consider using agent-browser to verify the result visually.
244
256
  `;
245
257
  var ChatSession = class {
246
258
  sessionId;
@@ -758,23 +770,14 @@ data: ${JSON.stringify(doneData)}
758
770
  `);
759
771
  res.end();
760
772
  }
761
- if (opts.env["VIAGEN_CALLBACK_URL"] && opts.env["VIAGEN_AUTH_TOKEN"]) {
773
+ if (opts.viagenClient && opts.projectId) {
762
774
  const taskId = opts.env["VIAGEN_TASK_ID"];
763
- if (taskId && (event.inputTokens || event.outputTokens || event.costUsd)) {
764
- fetch(opts.env["VIAGEN_CALLBACK_URL"], {
765
- method: "POST",
766
- headers: {
767
- "Content-Type": "application/json",
768
- Authorization: `Bearer ${opts.env["VIAGEN_AUTH_TOKEN"]}`
769
- },
770
- body: JSON.stringify({
771
- taskId,
772
- ...event.inputTokens != null && { inputTokens: event.inputTokens },
773
- ...event.outputTokens != null && { outputTokens: event.outputTokens },
774
- ...event.costUsd != null && { costUsd: event.costUsd }
775
- })
775
+ if (taskId && (event.inputTokens || event.outputTokens)) {
776
+ opts.viagenClient.tasks.update(opts.projectId, taskId, {
777
+ ...event.inputTokens != null && { inputTokens: event.inputTokens },
778
+ ...event.outputTokens != null && { outputTokens: event.outputTokens }
776
779
  }).catch((err) => {
777
- debug("chat", `usage callback failed: ${err}`);
780
+ debug("chat", `usage report failed: ${err}`);
778
781
  });
779
782
  }
780
783
  }
@@ -797,6 +800,213 @@ data: ${JSON.stringify(doneData)}
797
800
  }
798
801
 
799
802
  // src/overlay.ts
803
+ function buildPreviewScript() {
804
+ return (
805
+ /* js */
806
+ `
807
+ (function() {
808
+ if (document.getElementById('viagen-preview-btn')) return;
809
+
810
+ var btn = document.createElement('button');
811
+ btn.id = 'viagen-preview-btn';
812
+ btn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:inline-block;vertical-align:middle;margin-right:5px;"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg><span style="vertical-align:middle;">Feedback</span>';
813
+ btn.style.cssText = 'position:fixed;bottom:16px;left:16px;z-index:99998;padding:8px 14px;background:#ffffff;color:#525252;border:1px solid #e5e5e5;border-radius:20px;font-size:12px;font-weight:500;font-family:Geist,-apple-system,BlinkMacSystemFont,sans-serif;cursor:pointer;letter-spacing:-0.01em;transition:border-color 0.15s,color 0.15s,box-shadow 0.15s;box-shadow:0 1px 3px rgba(0,0,0,0.08),0 1px 2px rgba(0,0,0,0.04);display:flex;align-items:center;';
814
+ btn.onmouseenter = function() { btn.style.borderColor = '#d4d4d4'; btn.style.color = '#171717'; btn.style.boxShadow = '0 2px 6px rgba(0,0,0,0.1),0 1px 3px rgba(0,0,0,0.06)'; };
815
+ btn.onmouseleave = function() { btn.style.borderColor = '#e5e5e5'; btn.style.color = '#525252'; btn.style.boxShadow = '0 1px 3px rgba(0,0,0,0.08),0 1px 2px rgba(0,0,0,0.04)'; };
816
+
817
+ btn.addEventListener('click', function() { openFeedback(); });
818
+ document.body.appendChild(btn);
819
+
820
+ function loadHtml2Canvas() {
821
+ return new Promise(function(resolve, reject) {
822
+ if (typeof window.html2canvas !== 'undefined') { resolve(window.html2canvas); return; }
823
+ var s = document.createElement('script');
824
+ s.src = 'https://unpkg.com/html2canvas@1.4.1/dist/html2canvas.min.js';
825
+ s.onload = function() { resolve(window.html2canvas); };
826
+ s.onerror = function() { reject(new Error('Failed to load html2canvas')); };
827
+ document.head.appendChild(s);
828
+ });
829
+ }
830
+
831
+ function openFeedback() {
832
+ btn.disabled = true;
833
+ var origHTML = btn.innerHTML;
834
+ btn.innerHTML = '<span style="vertical-align:middle;">Capturing\u2026</span>';
835
+
836
+ loadHtml2Canvas().then(function(h2c) {
837
+ return h2c(document.documentElement, {
838
+ useCORS: true,
839
+ allowTaint: true,
840
+ logging: false,
841
+ scale: Math.min(window.devicePixelRatio || 1, 2),
842
+ });
843
+ }).then(function(canvas) {
844
+ var dataUrl = canvas.toDataURL('image/jpeg', 0.82);
845
+ btn.disabled = false;
846
+ btn.innerHTML = origHTML;
847
+ showModal(dataUrl);
848
+ }).catch(function(e) {
849
+ console.warn('[viagen-preview] Screenshot failed:', e);
850
+ btn.disabled = false;
851
+ btn.innerHTML = origHTML;
852
+ showModal(null);
853
+ });
854
+ }
855
+
856
+ function showModal(screenshotDataUrl) {
857
+ var overlay = document.createElement('div');
858
+ overlay.id = 'viagen-preview-modal';
859
+ overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:99999;display:flex;align-items:center;justify-content:center;font-family:Geist,-apple-system,BlinkMacSystemFont,sans-serif;';
860
+
861
+ var modal = document.createElement('div');
862
+ modal.style.cssText = 'background:#fff;border-radius:16px;padding:24px;max-width:560px;width:calc(100% - 32px);max-height:90vh;overflow-y:auto;box-shadow:0 20px 60px rgba(0,0,0,0.2);box-sizing:border-box;';
863
+
864
+ /* Header */
865
+ var header = document.createElement('div');
866
+ header.style.cssText = 'display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;';
867
+ var title = document.createElement('h2');
868
+ title.textContent = 'Send Feedback';
869
+ title.style.cssText = 'margin:0;font-size:16px;font-weight:600;color:#171717;';
870
+ var closeBtn = document.createElement('button');
871
+ closeBtn.innerHTML = '&times;';
872
+ closeBtn.style.cssText = 'background:none;border:none;font-size:22px;cursor:pointer;color:#a3a3a3;padding:0;line-height:1;transition:color 0.15s;';
873
+ closeBtn.onmouseenter = function() { closeBtn.style.color = '#171717'; };
874
+ closeBtn.onmouseleave = function() { closeBtn.style.color = '#a3a3a3'; };
875
+ closeBtn.onclick = function() { overlay.remove(); };
876
+ header.appendChild(title);
877
+ header.appendChild(closeBtn);
878
+ modal.appendChild(header);
879
+
880
+ /* Screenshot preview */
881
+ if (screenshotDataUrl) {
882
+ var imgWrap = document.createElement('div');
883
+ imgWrap.style.cssText = 'margin-bottom:16px;border-radius:8px;overflow:hidden;border:1px solid #e5e5e5;background:#f5f5f5;';
884
+ var img = document.createElement('img');
885
+ img.src = screenshotDataUrl;
886
+ img.style.cssText = 'width:100%;display:block;max-height:220px;object-fit:cover;object-position:top;';
887
+ imgWrap.appendChild(img);
888
+ modal.appendChild(imgWrap);
889
+ } else {
890
+ var pageRef = document.createElement('div');
891
+ pageRef.textContent = 'Page: ' + window.location.href;
892
+ pageRef.style.cssText = 'margin-bottom:16px;padding:10px 12px;background:#f5f5f5;border-radius:8px;font-size:12px;color:#737373;word-break:break-all;';
893
+ modal.appendChild(pageRef);
894
+ }
895
+
896
+ /* Textarea */
897
+ var label = document.createElement('label');
898
+ label.textContent = 'Describe your feedback';
899
+ label.style.cssText = 'display:block;font-size:13px;font-weight:500;color:#525252;margin-bottom:8px;';
900
+ modal.appendChild(label);
901
+
902
+ var textarea = document.createElement('textarea');
903
+ textarea.placeholder = 'e.g. The button color looks off, the layout breaks on mobile\u2026';
904
+ textarea.style.cssText = 'width:100%;min-height:96px;border:1px solid #e5e5e5;border-radius:8px;padding:12px;font-size:14px;font-family:inherit;resize:vertical;box-sizing:border-box;outline:none;transition:border-color 0.15s;color:#171717;';
905
+ textarea.onfocus = function() { textarea.style.borderColor = '#a3a3a3'; };
906
+ textarea.onblur = function() { textarea.style.borderColor = '#e5e5e5'; };
907
+ modal.appendChild(textarea);
908
+
909
+ /* Status */
910
+ var statusEl = document.createElement('div');
911
+ statusEl.style.cssText = 'margin-top:10px;font-size:13px;color:#737373;display:none;min-height:20px;';
912
+ modal.appendChild(statusEl);
913
+
914
+ /* Buttons */
915
+ var btnRow = document.createElement('div');
916
+ btnRow.style.cssText = 'display:flex;gap:8px;margin-top:16px;justify-content:flex-end;';
917
+ var cancelBtn = document.createElement('button');
918
+ cancelBtn.textContent = 'Cancel';
919
+ cancelBtn.style.cssText = 'padding:9px 16px;background:#f5f5f5;color:#525252;border:none;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;font-family:inherit;transition:background 0.15s;';
920
+ cancelBtn.onmouseenter = function() { cancelBtn.style.background = '#e5e5e5'; };
921
+ cancelBtn.onmouseleave = function() { cancelBtn.style.background = '#f5f5f5'; };
922
+ cancelBtn.onclick = function() { overlay.remove(); };
923
+ var submitBtn = document.createElement('button');
924
+ submitBtn.textContent = 'Create Task';
925
+ submitBtn.style.cssText = 'padding:9px 16px;background:#171717;color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;font-family:inherit;transition:background 0.15s;';
926
+ submitBtn.onmouseenter = function() { if (!submitBtn.disabled) submitBtn.style.background = '#404040'; };
927
+ submitBtn.onmouseleave = function() { if (!submitBtn.disabled) submitBtn.style.background = '#171717'; };
928
+
929
+ submitBtn.addEventListener('click', function() {
930
+ var feedback = textarea.value.trim();
931
+ if (!feedback) {
932
+ textarea.style.borderColor = '#ef4444';
933
+ textarea.focus();
934
+ return;
935
+ }
936
+ submitBtn.disabled = true;
937
+ cancelBtn.disabled = true;
938
+ submitBtn.textContent = 'Creating\u2026';
939
+ submitBtn.style.opacity = '0.7';
940
+ statusEl.style.display = 'block';
941
+ statusEl.style.color = '#737373';
942
+ statusEl.textContent = 'Submitting your feedback\u2026';
943
+
944
+ var payload = {
945
+ prompt: feedback,
946
+ pageUrl: window.location.href,
947
+ screenshot: screenshotDataUrl || undefined,
948
+ };
949
+
950
+ fetch('/via/preview/task', {
951
+ method: 'POST',
952
+ headers: { 'Content-Type': 'application/json' },
953
+ body: JSON.stringify(payload),
954
+ }).then(function(res) {
955
+ if (!res.ok) {
956
+ return res.json().catch(function() { return {}; }).then(function(e) {
957
+ throw new Error(e.error || 'HTTP ' + res.status);
958
+ });
959
+ }
960
+ return res.json();
961
+ }).then(function() {
962
+ /* Success state */
963
+ modal.innerHTML = '';
964
+ var successDiv = document.createElement('div');
965
+ successDiv.style.cssText = 'text-align:center;padding:32px 24px;';
966
+ var check = document.createElement('div');
967
+ check.style.cssText = 'width:48px;height:48px;background:#22c55e;border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 16px;color:#fff;font-size:22px;font-weight:700;';
968
+ check.textContent = '\\u2713';
969
+ var h3 = document.createElement('h3');
970
+ h3.textContent = 'Task Created!';
971
+ h3.style.cssText = 'margin:0 0 8px;font-size:16px;font-weight:600;color:#171717;font-family:Geist,-apple-system,BlinkMacSystemFont,sans-serif;';
972
+ var p = document.createElement('p');
973
+ p.textContent = 'Your feedback has been submitted successfully.';
974
+ p.style.cssText = 'margin:0;color:#737373;font-size:14px;font-family:Geist,-apple-system,BlinkMacSystemFont,sans-serif;';
975
+ successDiv.appendChild(check);
976
+ successDiv.appendChild(h3);
977
+ successDiv.appendChild(p);
978
+ modal.appendChild(successDiv);
979
+ setTimeout(function() { overlay.remove(); }, 2500);
980
+ }).catch(function(err) {
981
+ submitBtn.disabled = false;
982
+ cancelBtn.disabled = false;
983
+ submitBtn.textContent = 'Create Task';
984
+ submitBtn.style.opacity = '1';
985
+ statusEl.style.color = '#ef4444';
986
+ statusEl.textContent = 'Error: ' + (err.message || 'Failed to submit. Please try again.');
987
+ });
988
+ });
989
+
990
+ btnRow.appendChild(cancelBtn);
991
+ btnRow.appendChild(submitBtn);
992
+ modal.appendChild(btnRow);
993
+ overlay.appendChild(modal);
994
+
995
+ overlay.addEventListener('click', function(e) {
996
+ if (e.target === overlay) overlay.remove();
997
+ });
998
+ var escHandler = function(e) {
999
+ if (e.key === 'Escape') { overlay.remove(); document.removeEventListener('keydown', escHandler); }
1000
+ };
1001
+ document.addEventListener('keydown', escHandler);
1002
+
1003
+ document.body.appendChild(overlay);
1004
+ setTimeout(function() { textarea.focus(); }, 50);
1005
+ }
1006
+ })();
1007
+ `
1008
+ );
1009
+ }
800
1010
  function buildClientScript(opts) {
801
1011
  const pos = opts.position;
802
1012
  const pw = opts.panelWidth;
@@ -4334,7 +4544,9 @@ function registerFileRoutes(server, opts) {
4334
4544
  // src/inject.ts
4335
4545
  var SCRIPT_TAG = '<script src="/via/client.js" defer></script>';
4336
4546
  var MARKER = "viagen-toggle";
4337
- function createInjectionMiddleware() {
4547
+ var PREVIEW_SCRIPT_TAG = '<script src="/via/preview.js" defer></script>';
4548
+ var PREVIEW_MARKER = "viagen-preview-btn";
4549
+ function createScriptInjectionMiddleware(scriptTag, marker) {
4338
4550
  return function injectMiddleware(req, res, next) {
4339
4551
  if (req.method !== "GET" && req.method !== "HEAD") {
4340
4552
  return next();
@@ -4359,7 +4571,7 @@ function createInjectionMiddleware() {
4359
4571
  if (!isHtmlResponse()) return chunk;
4360
4572
  const str = typeof chunk === "string" ? chunk : Buffer.isBuffer(chunk) ? chunk.toString("utf-8") : null;
4361
4573
  if (!str) return chunk;
4362
- if (str.includes(MARKER) || str.includes("/via/client.js")) {
4574
+ if (str.includes(marker) || str.includes(scriptTag)) {
4363
4575
  injected = true;
4364
4576
  return chunk;
4365
4577
  }
@@ -4370,7 +4582,7 @@ function createInjectionMiddleware() {
4370
4582
  if (!res.headersSent) {
4371
4583
  res.removeHeader("content-length");
4372
4584
  }
4373
- const result = str.slice(0, idx) + SCRIPT_TAG + str.slice(idx);
4585
+ const result = str.slice(0, idx) + scriptTag + str.slice(idx);
4374
4586
  return typeof chunk === "string" ? result : Buffer.from(result, "utf-8");
4375
4587
  }
4376
4588
  res.write = function(chunk, ...args) {
@@ -4390,6 +4602,12 @@ function createInjectionMiddleware() {
4390
4602
  next();
4391
4603
  };
4392
4604
  }
4605
+ function createInjectionMiddleware() {
4606
+ return createScriptInjectionMiddleware(SCRIPT_TAG, MARKER);
4607
+ }
4608
+ function createPreviewInjectionMiddleware() {
4609
+ return createScriptInjectionMiddleware(PREVIEW_SCRIPT_TAG, PREVIEW_MARKER);
4610
+ }
4393
4611
 
4394
4612
  // src/git.ts
4395
4613
  import { readFileSync as readFileSync3 } from "fs";
@@ -18433,17 +18651,14 @@ function date4(params) {
18433
18651
  // node_modules/zod/v4/classic/external.js
18434
18652
  config(en_default());
18435
18653
 
18436
- // src/viagen-tools.ts
18654
+ // src/tools.ts
18437
18655
  import {
18438
18656
  createSdkMcpServer,
18439
18657
  tool
18440
18658
  } from "@anthropic-ai/claude-agent-sdk";
18441
- import {
18442
- listTasks,
18443
- getTask,
18444
- createTask
18445
- } from "viagen-sdk/sandbox";
18446
18659
  function createViagenTools(config2) {
18660
+ const { client, projectId } = config2;
18661
+ const taskId = process.env["VIAGEN_TASK_ID"];
18447
18662
  const tools = [
18448
18663
  tool(
18449
18664
  "viagen_update_task",
@@ -18459,112 +18674,94 @@ function createViagenTools(config2) {
18459
18674
  prReviewStatus: external_exports.string().optional().describe("PR review outcome \u2014 e.g. 'pass', 'flag', or 'fail'.")
18460
18675
  },
18461
18676
  async (args) => {
18462
- const taskId = args.taskId || process.env["VIAGEN_TASK_ID"];
18463
- if (!taskId) {
18677
+ const id = args.taskId || taskId;
18678
+ if (!id) {
18464
18679
  return {
18465
18680
  content: [{ type: "text", text: "Error: No taskId provided and VIAGEN_TASK_ID is not set." }]
18466
18681
  };
18467
18682
  }
18468
- const callbackUrl = process.env["VIAGEN_CALLBACK_URL"];
18469
- const authToken = process.env["VIAGEN_AUTH_TOKEN"];
18470
18683
  const internalStatus = args.status === "review" ? "validating" : args.status === "completed" ? "completed" : args.status;
18471
- const res = await fetch(callbackUrl, {
18472
- method: "POST",
18473
- headers: {
18474
- "Content-Type": "application/json",
18475
- Authorization: `Bearer ${authToken}`
18476
- },
18477
- body: JSON.stringify({
18478
- taskId,
18684
+ try {
18685
+ await client.tasks.update(projectId, id, {
18479
18686
  ...internalStatus && { status: internalStatus },
18480
18687
  ...args.prUrl && { prUrl: args.prUrl },
18481
18688
  result: args.result,
18482
18689
  ...args.inputTokens != null && { inputTokens: args.inputTokens },
18483
18690
  ...args.outputTokens != null && { outputTokens: args.outputTokens },
18484
- ...args.costUsd != null && { costUsd: args.costUsd },
18485
18691
  ...args.prReviewStatus && { prReviewStatus: args.prReviewStatus }
18486
- })
18487
- });
18488
- if (!res.ok) {
18489
- const text = await res.text().catch(() => "");
18692
+ });
18490
18693
  return {
18491
- content: [{ type: "text", text: `Error updating task (${res.status}): ${text}` }]
18694
+ content: [{ type: "text", text: `Task ${id} updated.${args.status ? ` Status: '${args.status}'.` : ""}${args.prReviewStatus ? ` PR review: '${args.prReviewStatus}'.` : ""}` }]
18695
+ };
18696
+ } catch (err) {
18697
+ const message = err instanceof Error ? err.message : "Unknown error";
18698
+ return {
18699
+ content: [{ type: "text", text: `Error updating task: ${message}` }]
18492
18700
  };
18493
18701
  }
18702
+ }
18703
+ ),
18704
+ tool(
18705
+ "viagen_list_tasks",
18706
+ "List tasks in the current project. Optionally filter by status.",
18707
+ {
18708
+ status: external_exports.enum(["ready", "running", "validating", "completed", "timed_out"]).optional().describe("Filter tasks by status.")
18709
+ },
18710
+ async (args) => {
18711
+ const tasks = await client.tasks.list(projectId, args.status);
18494
18712
  return {
18495
- content: [{ type: "text", text: `Task ${taskId} updated.${args.status ? ` Status: '${args.status}'.` : ""}${args.prReviewStatus ? ` PR review: '${args.prReviewStatus}'.` : ""}` }]
18713
+ content: [
18714
+ {
18715
+ type: "text",
18716
+ text: JSON.stringify(tasks, null, 2)
18717
+ }
18718
+ ]
18719
+ };
18720
+ }
18721
+ ),
18722
+ tool(
18723
+ "viagen_get_task",
18724
+ "Get full details of a specific task by ID.",
18725
+ {
18726
+ taskId: external_exports.string().describe("The task ID to retrieve.")
18727
+ },
18728
+ async (args) => {
18729
+ const task = await client.tasks.get(projectId, args.taskId);
18730
+ return {
18731
+ content: [
18732
+ {
18733
+ type: "text",
18734
+ text: JSON.stringify(task, null, 2)
18735
+ }
18736
+ ]
18737
+ };
18738
+ }
18739
+ ),
18740
+ tool(
18741
+ "viagen_create_task",
18742
+ "Create a new task in the current project. Use this to create follow-up work.",
18743
+ {
18744
+ prompt: external_exports.string().describe("The task prompt / instructions."),
18745
+ branch: external_exports.string().optional().describe("Git branch name for the task."),
18746
+ type: external_exports.enum(["task", "plan"]).optional().describe("Task type: 'task' for code changes, 'plan' for implementation plans.")
18747
+ },
18748
+ async (args) => {
18749
+ const task = await client.tasks.create(projectId, {
18750
+ prompt: args.prompt,
18751
+ branch: args.branch,
18752
+ type: args.type
18753
+ });
18754
+ return {
18755
+ content: [
18756
+ {
18757
+ type: "text",
18758
+ text: JSON.stringify(task, null, 2)
18759
+ }
18760
+ ]
18496
18761
  };
18497
18762
  }
18498
18763
  )
18499
18764
  ];
18500
- if (config2?.projectId) {
18501
- tools.push(
18502
- tool(
18503
- "viagen_list_tasks",
18504
- "List tasks in the current project. Optionally filter by status.",
18505
- {
18506
- status: external_exports.enum(["ready", "running", "validating", "completed", "timed_out"]).optional().describe("Filter tasks by status.")
18507
- },
18508
- async (args) => {
18509
- const tasks = await listTasks({ status: args.status });
18510
- return {
18511
- content: [
18512
- {
18513
- type: "text",
18514
- text: JSON.stringify(tasks, null, 2)
18515
- }
18516
- ]
18517
- };
18518
- }
18519
- )
18520
- );
18521
- tools.push(
18522
- tool(
18523
- "viagen_get_task",
18524
- "Get full details of a specific task by ID.",
18525
- {
18526
- taskId: external_exports.string().describe("The task ID to retrieve.")
18527
- },
18528
- async (args) => {
18529
- const task = await getTask(args.taskId);
18530
- return {
18531
- content: [
18532
- {
18533
- type: "text",
18534
- text: JSON.stringify(task, null, 2)
18535
- }
18536
- ]
18537
- };
18538
- }
18539
- )
18540
- );
18541
- tools.push(
18542
- tool(
18543
- "viagen_create_task",
18544
- "Create a new task in the current project. Use this to create follow-up work.",
18545
- {
18546
- prompt: external_exports.string().describe("The task prompt / instructions."),
18547
- branch: external_exports.string().optional().describe("Git branch name for the task."),
18548
- type: external_exports.enum(["task", "plan"]).optional().describe("Task type: 'task' for code changes, 'plan' for implementation plans.")
18549
- },
18550
- async (args) => {
18551
- const task = await createTask({
18552
- prompt: args.prompt,
18553
- branch: args.branch,
18554
- type: args.type
18555
- });
18556
- return {
18557
- content: [
18558
- {
18559
- type: "text",
18560
- text: JSON.stringify(task, null, 2)
18561
- }
18562
- ]
18563
- };
18564
- }
18565
- )
18566
- );
18567
- }
18568
18765
  return createSdkMcpServer({ name: "viagen", tools });
18569
18766
  }
18570
18767
  var PLAN_MODE_DISALLOWED_TOOLS = ["Edit", "NotebookEdit"];
@@ -18604,6 +18801,9 @@ You have access to viagen platform tools for task management:
18604
18801
  Use these to understand project context and create follow-up work when appropriate.
18605
18802
  `;
18606
18803
 
18804
+ // src/index.ts
18805
+ import { createViagen } from "viagen-sdk";
18806
+
18607
18807
  // src/sandbox.ts
18608
18808
  import { randomUUID } from "crypto";
18609
18809
  import { readFileSync as readFileSync4, readdirSync as readdirSync2 } from "fs";
@@ -18868,6 +19068,7 @@ function viagen(options) {
18868
19068
  ui: options?.ui ?? true
18869
19069
  };
18870
19070
  let env;
19071
+ let previewEnabled = false;
18871
19072
  let projectRoot;
18872
19073
  let lastError = null;
18873
19074
  let promptSent = false;
@@ -18877,12 +19078,13 @@ function viagen(options) {
18877
19078
  name: "viagen",
18878
19079
  config(_, { mode }) {
18879
19080
  const e = loadEnv(mode, process.cwd(), "");
18880
- if (e["VIAGEN_AUTH_TOKEN"]) {
19081
+ if (e["VIAGEN_AUTH_TOKEN"] || e["VIAGEN_USER_TOKEN"]) {
18881
19082
  return { server: { host: true, allowedHosts: true } };
18882
19083
  }
18883
19084
  },
18884
19085
  configResolved(config2) {
18885
19086
  env = loadEnv(config2.mode, config2.envDir ?? config2.root, "");
19087
+ previewEnabled = env["VIAGEN_PREVIEW"] === "true";
18886
19088
  projectRoot = config2.root;
18887
19089
  const debugEnabled = options?.debug ?? env["VIAGEN_DEBUG"] === "1";
18888
19090
  setDebug(debugEnabled);
@@ -18893,6 +19095,7 @@ function viagen(options) {
18893
19095
  debug("init", `CLAUDE_ACCESS_TOKEN: ${env["CLAUDE_ACCESS_TOKEN"] ? "set" : "NOT SET"}`);
18894
19096
  debug("init", `GITHUB_TOKEN: ${env["GITHUB_TOKEN"] ? "set" : "NOT SET"}`);
18895
19097
  debug("init", `VIAGEN_AUTH_TOKEN: ${env["VIAGEN_AUTH_TOKEN"] ? "set" : "NOT SET"}`);
19098
+ debug("init", `VIAGEN_USER_TOKEN: ${env["VIAGEN_USER_TOKEN"] ? "set" : "NOT SET"}`);
18896
19099
  debug("init", `VIAGEN_MODEL: ${env["VIAGEN_MODEL"] || "(not set)"}`);
18897
19100
  debug("init", `VIAGEN_PROMPT: ${env["VIAGEN_PROMPT"] ? `"${env["VIAGEN_PROMPT"].slice(0, 80)}..."` : "(not set)"}`);
18898
19101
  debug("init", `VIAGEN_TASK_ID: ${env["VIAGEN_TASK_ID"] || "(not set)"}`);
@@ -18911,21 +19114,30 @@ function viagen(options) {
18911
19114
  );
18912
19115
  },
18913
19116
  transformIndexHtml(_html, ctx) {
18914
- if (!opts.ui) return [];
18915
- const url2 = new URL(ctx.originalUrl || ctx.path, "http://localhost");
18916
- const isEmbed = url2.searchParams.has("_viagen_embed");
18917
- if (isEmbed && !opts.overlay) return [];
18918
- return [
18919
- {
19117
+ const tags = [];
19118
+ if (opts.ui) {
19119
+ const url2 = new URL(ctx.originalUrl || ctx.path, "http://localhost");
19120
+ const isEmbed = url2.searchParams.has("_viagen_embed");
19121
+ if (!isEmbed || opts.overlay) {
19122
+ tags.push({
19123
+ tag: "script",
19124
+ children: buildClientScript({
19125
+ position: opts.position,
19126
+ panelWidth: opts.panelWidth,
19127
+ overlay: opts.overlay
19128
+ }),
19129
+ injectTo: "body"
19130
+ });
19131
+ }
19132
+ }
19133
+ if (previewEnabled) {
19134
+ tags.push({
18920
19135
  tag: "script",
18921
- children: buildClientScript({
18922
- position: opts.position,
18923
- panelWidth: opts.panelWidth,
18924
- overlay: opts.overlay
18925
- }),
19136
+ children: buildPreviewScript(),
18926
19137
  injectTo: "body"
18927
- }
18928
- ];
19138
+ });
19139
+ }
19140
+ return tags;
18929
19141
  },
18930
19142
  configureServer(server) {
18931
19143
  debug("server", "configureServer starting");
@@ -18968,6 +19180,14 @@ ${payload.err.frame || ""}`
18968
19180
  } else {
18969
19181
  debug("server", "auth middleware DISABLED (no VIAGEN_AUTH_TOKEN)");
18970
19182
  }
19183
+ const platformToken = env["VIAGEN_USER_TOKEN"] || env["VIAGEN_AUTH_TOKEN"];
19184
+ const platformUrl = env["VIAGEN_PLATFORM_URL"] || "https://app.viagen.dev";
19185
+ const projectId = env["VIAGEN_PROJECT_ID"];
19186
+ let viagenClient = null;
19187
+ if (platformToken) {
19188
+ viagenClient = createViagen({ token: platformToken, baseUrl: platformUrl });
19189
+ debug("server", `platform client created (baseUrl: ${platformUrl})`);
19190
+ }
18971
19191
  const hasEditor = !!(options?.editable && options.editable.length > 0);
18972
19192
  const clientJs = buildClientScript({
18973
19193
  position: opts.position,
@@ -18990,18 +19210,80 @@ ${payload.err.frame || ""}`
18990
19210
  res.setHeader("Content-Type", "text/html");
18991
19211
  res.end(buildIframeHtml({ panelWidth: opts.panelWidth }));
18992
19212
  });
19213
+ if (previewEnabled) {
19214
+ const previewJs = buildPreviewScript();
19215
+ server.middlewares.use("/via/preview.js", (_req, res) => {
19216
+ res.setHeader("Content-Type", "application/javascript");
19217
+ res.end(previewJs);
19218
+ });
19219
+ server.middlewares.use("/via/preview/task", (req, res) => {
19220
+ if (req.method !== "POST") {
19221
+ res.writeHead(405, { "Content-Type": "application/json" });
19222
+ res.end(JSON.stringify({ error: "Method not allowed" }));
19223
+ return;
19224
+ }
19225
+ if (!viagenClient || !projectId) {
19226
+ res.writeHead(503, { "Content-Type": "application/json" });
19227
+ res.end(JSON.stringify({ error: "Task creation not configured: missing platform credentials or VIAGEN_PROJECT_ID" }));
19228
+ return;
19229
+ }
19230
+ let body = "";
19231
+ req.on("data", (chunk) => {
19232
+ body += chunk.toString();
19233
+ });
19234
+ req.on("end", async () => {
19235
+ try {
19236
+ const data = JSON.parse(body);
19237
+ const { prompt, pageUrl, screenshot } = data;
19238
+ if (!prompt || typeof prompt !== "string") {
19239
+ res.writeHead(400, { "Content-Type": "application/json" });
19240
+ res.end(JSON.stringify({ error: "prompt is required" }));
19241
+ return;
19242
+ }
19243
+ const parts = [prompt.trim()];
19244
+ if (pageUrl) parts.push(`
19245
+ Page URL: ${pageUrl}`);
19246
+ if (screenshot) parts.push("\n[Screenshot attached]");
19247
+ parts.push("\n\n[Submitted via viagen preview feedback]");
19248
+ const fullPrompt = parts.join("");
19249
+ const task = await viagenClient.tasks.create(projectId, { prompt: fullPrompt, type: "task" });
19250
+ if (screenshot && task.id) {
19251
+ try {
19252
+ const [header, b64] = screenshot.split(",");
19253
+ const mime = header.match(/:(.*?);/)?.[1] || "image/jpeg";
19254
+ const binary = Buffer.from(b64, "base64");
19255
+ const blob = new Blob([binary], { type: mime });
19256
+ const ext = mime === "image/png" ? "png" : "jpeg";
19257
+ await viagenClient.tasks.addAttachment(projectId, task.id, blob, `screenshot.${ext}`);
19258
+ debug("preview", `screenshot attached to task ${task.id}`);
19259
+ } catch (attachErr) {
19260
+ debug("preview", `screenshot attachment failed (non-fatal): ${attachErr}`);
19261
+ }
19262
+ }
19263
+ res.writeHead(200, { "Content-Type": "application/json" });
19264
+ res.end(JSON.stringify({ task }));
19265
+ } catch (err) {
19266
+ const message = err instanceof Error ? err.message : "Internal error";
19267
+ debug("preview", `preview/task error: ${message}`);
19268
+ res.writeHead(500, { "Content-Type": "application/json" });
19269
+ res.end(JSON.stringify({ error: message }));
19270
+ }
19271
+ });
19272
+ });
19273
+ }
18993
19274
  registerHealthRoutes(server, env, {
18994
19275
  get: () => lastError
18995
19276
  });
18996
19277
  const resolvedModel = env["VIAGEN_MODEL"] || opts.model;
18997
19278
  debug("server", `creating ChatSession (model: ${resolvedModel})`);
18998
19279
  let mcpServers;
18999
- const hasSandboxContext = !!(env["VIAGEN_CALLBACK_URL"] && env["VIAGEN_AUTH_TOKEN"]);
19000
- if (hasSandboxContext) {
19001
- debug("server", "creating viagen MCP tools (sandbox mode)");
19002
- const viagenMcp = createViagenTools(
19003
- env["VIAGEN_PROJECT_ID"] ? { projectId: env["VIAGEN_PROJECT_ID"] } : void 0
19004
- );
19280
+ const hasPlatformContext = !!(viagenClient && projectId);
19281
+ if (hasPlatformContext) {
19282
+ debug("server", "creating viagen MCP tools (platform connected)");
19283
+ const viagenMcp = createViagenTools({
19284
+ client: viagenClient,
19285
+ projectId
19286
+ });
19005
19287
  mcpServers = { [viagenMcp.name]: viagenMcp };
19006
19288
  }
19007
19289
  const isPlanMode = env["VIAGEN_TASK_TYPE"] === "plan";
@@ -19009,7 +19291,7 @@ ${payload.err.frame || ""}`
19009
19291
  if (isPlanMode) {
19010
19292
  debug("server", "plan mode active \u2014 restricting tools");
19011
19293
  systemPrompt = PLAN_SYSTEM_PROMPT;
19012
- } else if (hasSandboxContext && env["VIAGEN_PROJECT_ID"]) {
19294
+ } else if (hasPlatformContext) {
19013
19295
  systemPrompt = (systemPrompt || "") + TASK_TOOLS_PROMPT;
19014
19296
  }
19015
19297
  const chatSession = new ChatSession({
@@ -19025,7 +19307,11 @@ ${payload.err.frame || ""}`
19025
19307
  } : {}
19026
19308
  });
19027
19309
  debug("server", "registering chat routes");
19028
- registerChatRoutes(server, chatSession, { env });
19310
+ registerChatRoutes(server, chatSession, {
19311
+ env,
19312
+ viagenClient: viagenClient ?? void 0,
19313
+ projectId
19314
+ });
19029
19315
  if (hasEditor) {
19030
19316
  registerFileRoutes(server, {
19031
19317
  editable: options.editable,
@@ -19043,30 +19329,26 @@ ${payload.err.frame || ""}`
19043
19329
  if (event.type === "done") {
19044
19330
  debug("server", "auto-prompt completed");
19045
19331
  logBuffer.push("info", `[viagen] Prompt completed`);
19046
- const taskId = env["VIAGEN_TASK_ID"];
19047
- if (hasSandboxContext && taskId && (event.inputTokens || event.outputTokens || event.costUsd)) {
19048
- fetch(env["VIAGEN_CALLBACK_URL"], {
19049
- method: "POST",
19050
- headers: {
19051
- "Content-Type": "application/json",
19052
- Authorization: `Bearer ${env["VIAGEN_AUTH_TOKEN"]}`
19053
- },
19054
- body: JSON.stringify({
19055
- taskId,
19056
- ...event.inputTokens != null && { inputTokens: event.inputTokens },
19057
- ...event.outputTokens != null && { outputTokens: event.outputTokens },
19058
- ...event.costUsd != null && { costUsd: event.costUsd }
19059
- })
19332
+ const currentTaskId = env["VIAGEN_TASK_ID"];
19333
+ if (viagenClient && projectId && currentTaskId && (event.inputTokens || event.outputTokens)) {
19334
+ viagenClient.tasks.update(projectId, currentTaskId, {
19335
+ ...event.inputTokens != null && { inputTokens: event.inputTokens },
19336
+ ...event.outputTokens != null && { outputTokens: event.outputTokens }
19060
19337
  }).catch((err) => {
19061
- debug("server", `usage callback failed: ${err}`);
19338
+ debug("server", `usage report failed: ${err}`);
19062
19339
  });
19063
19340
  }
19064
19341
  }
19065
19342
  });
19066
19343
  }
19067
- if (opts.ui) {
19344
+ if (opts.ui || previewEnabled) {
19068
19345
  return () => {
19069
- server.middlewares.use(createInjectionMiddleware());
19346
+ if (opts.ui) {
19347
+ server.middlewares.use(createInjectionMiddleware());
19348
+ }
19349
+ if (previewEnabled) {
19350
+ server.middlewares.use(createPreviewInjectionMiddleware());
19351
+ }
19070
19352
  };
19071
19353
  }
19072
19354
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "viagen",
3
- "version": "0.0.53",
3
+ "version": "0.0.55",
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",
@@ -42,9 +42,10 @@
42
42
  "@anthropic-ai/claude-agent-sdk": "^0.2.50",
43
43
  "@anthropic-ai/claude-code": "^2.1.42",
44
44
  "@vercel/sandbox": "^1",
45
+ "agent-browser": "^0.20.12",
45
46
  "lucide-react": "^0.564.0",
46
47
  "simple-git": "^3.31.1",
47
- "viagen-sdk": "^0.0.11"
48
+ "viagen-sdk": "^0.0.12"
48
49
  },
49
50
  "license": "MIT",
50
51
  "repository": {