viagen 0.0.53 → 0.0.54

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.
Files changed (2) hide show
  1. package/dist/index.js +421 -151
  2. package/package.json +2 -2
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.54" : "0.0.0";
114
114
  debug("health", `version resolved: ${currentVersion}`);
115
115
  let versionCache = null;
116
116
  server.middlewares.use("/via/version", (_req, res) => {
@@ -758,23 +758,14 @@ data: ${JSON.stringify(doneData)}
758
758
  `);
759
759
  res.end();
760
760
  }
761
- if (opts.env["VIAGEN_CALLBACK_URL"] && opts.env["VIAGEN_AUTH_TOKEN"]) {
761
+ if (opts.viagenClient && opts.projectId) {
762
762
  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
- })
763
+ if (taskId && (event.inputTokens || event.outputTokens)) {
764
+ opts.viagenClient.tasks.update(opts.projectId, taskId, {
765
+ ...event.inputTokens != null && { inputTokens: event.inputTokens },
766
+ ...event.outputTokens != null && { outputTokens: event.outputTokens }
776
767
  }).catch((err) => {
777
- debug("chat", `usage callback failed: ${err}`);
768
+ debug("chat", `usage report failed: ${err}`);
778
769
  });
779
770
  }
780
771
  }
@@ -797,6 +788,213 @@ data: ${JSON.stringify(doneData)}
797
788
  }
798
789
 
799
790
  // src/overlay.ts
791
+ function buildPreviewScript() {
792
+ return (
793
+ /* js */
794
+ `
795
+ (function() {
796
+ if (document.getElementById('viagen-preview-btn')) return;
797
+
798
+ var btn = document.createElement('button');
799
+ btn.id = 'viagen-preview-btn';
800
+ 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>';
801
+ 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;';
802
+ 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)'; };
803
+ 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)'; };
804
+
805
+ btn.addEventListener('click', function() { openFeedback(); });
806
+ document.body.appendChild(btn);
807
+
808
+ function loadHtml2Canvas() {
809
+ return new Promise(function(resolve, reject) {
810
+ if (typeof window.html2canvas !== 'undefined') { resolve(window.html2canvas); return; }
811
+ var s = document.createElement('script');
812
+ s.src = 'https://unpkg.com/html2canvas@1.4.1/dist/html2canvas.min.js';
813
+ s.onload = function() { resolve(window.html2canvas); };
814
+ s.onerror = function() { reject(new Error('Failed to load html2canvas')); };
815
+ document.head.appendChild(s);
816
+ });
817
+ }
818
+
819
+ function openFeedback() {
820
+ btn.disabled = true;
821
+ var origHTML = btn.innerHTML;
822
+ btn.innerHTML = '<span style="vertical-align:middle;">Capturing\u2026</span>';
823
+
824
+ loadHtml2Canvas().then(function(h2c) {
825
+ return h2c(document.documentElement, {
826
+ useCORS: true,
827
+ allowTaint: true,
828
+ logging: false,
829
+ scale: Math.min(window.devicePixelRatio || 1, 2),
830
+ });
831
+ }).then(function(canvas) {
832
+ var dataUrl = canvas.toDataURL('image/jpeg', 0.82);
833
+ btn.disabled = false;
834
+ btn.innerHTML = origHTML;
835
+ showModal(dataUrl);
836
+ }).catch(function(e) {
837
+ console.warn('[viagen-preview] Screenshot failed:', e);
838
+ btn.disabled = false;
839
+ btn.innerHTML = origHTML;
840
+ showModal(null);
841
+ });
842
+ }
843
+
844
+ function showModal(screenshotDataUrl) {
845
+ var overlay = document.createElement('div');
846
+ overlay.id = 'viagen-preview-modal';
847
+ 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;';
848
+
849
+ var modal = document.createElement('div');
850
+ 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;';
851
+
852
+ /* Header */
853
+ var header = document.createElement('div');
854
+ header.style.cssText = 'display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;';
855
+ var title = document.createElement('h2');
856
+ title.textContent = 'Send Feedback';
857
+ title.style.cssText = 'margin:0;font-size:16px;font-weight:600;color:#171717;';
858
+ var closeBtn = document.createElement('button');
859
+ closeBtn.innerHTML = '&times;';
860
+ closeBtn.style.cssText = 'background:none;border:none;font-size:22px;cursor:pointer;color:#a3a3a3;padding:0;line-height:1;transition:color 0.15s;';
861
+ closeBtn.onmouseenter = function() { closeBtn.style.color = '#171717'; };
862
+ closeBtn.onmouseleave = function() { closeBtn.style.color = '#a3a3a3'; };
863
+ closeBtn.onclick = function() { overlay.remove(); };
864
+ header.appendChild(title);
865
+ header.appendChild(closeBtn);
866
+ modal.appendChild(header);
867
+
868
+ /* Screenshot preview */
869
+ if (screenshotDataUrl) {
870
+ var imgWrap = document.createElement('div');
871
+ imgWrap.style.cssText = 'margin-bottom:16px;border-radius:8px;overflow:hidden;border:1px solid #e5e5e5;background:#f5f5f5;';
872
+ var img = document.createElement('img');
873
+ img.src = screenshotDataUrl;
874
+ img.style.cssText = 'width:100%;display:block;max-height:220px;object-fit:cover;object-position:top;';
875
+ imgWrap.appendChild(img);
876
+ modal.appendChild(imgWrap);
877
+ } else {
878
+ var pageRef = document.createElement('div');
879
+ pageRef.textContent = 'Page: ' + window.location.href;
880
+ pageRef.style.cssText = 'margin-bottom:16px;padding:10px 12px;background:#f5f5f5;border-radius:8px;font-size:12px;color:#737373;word-break:break-all;';
881
+ modal.appendChild(pageRef);
882
+ }
883
+
884
+ /* Textarea */
885
+ var label = document.createElement('label');
886
+ label.textContent = 'Describe your feedback';
887
+ label.style.cssText = 'display:block;font-size:13px;font-weight:500;color:#525252;margin-bottom:8px;';
888
+ modal.appendChild(label);
889
+
890
+ var textarea = document.createElement('textarea');
891
+ textarea.placeholder = 'e.g. The button color looks off, the layout breaks on mobile\u2026';
892
+ 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;';
893
+ textarea.onfocus = function() { textarea.style.borderColor = '#a3a3a3'; };
894
+ textarea.onblur = function() { textarea.style.borderColor = '#e5e5e5'; };
895
+ modal.appendChild(textarea);
896
+
897
+ /* Status */
898
+ var statusEl = document.createElement('div');
899
+ statusEl.style.cssText = 'margin-top:10px;font-size:13px;color:#737373;display:none;min-height:20px;';
900
+ modal.appendChild(statusEl);
901
+
902
+ /* Buttons */
903
+ var btnRow = document.createElement('div');
904
+ btnRow.style.cssText = 'display:flex;gap:8px;margin-top:16px;justify-content:flex-end;';
905
+ var cancelBtn = document.createElement('button');
906
+ cancelBtn.textContent = 'Cancel';
907
+ 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;';
908
+ cancelBtn.onmouseenter = function() { cancelBtn.style.background = '#e5e5e5'; };
909
+ cancelBtn.onmouseleave = function() { cancelBtn.style.background = '#f5f5f5'; };
910
+ cancelBtn.onclick = function() { overlay.remove(); };
911
+ var submitBtn = document.createElement('button');
912
+ submitBtn.textContent = 'Create Task';
913
+ 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;';
914
+ submitBtn.onmouseenter = function() { if (!submitBtn.disabled) submitBtn.style.background = '#404040'; };
915
+ submitBtn.onmouseleave = function() { if (!submitBtn.disabled) submitBtn.style.background = '#171717'; };
916
+
917
+ submitBtn.addEventListener('click', function() {
918
+ var feedback = textarea.value.trim();
919
+ if (!feedback) {
920
+ textarea.style.borderColor = '#ef4444';
921
+ textarea.focus();
922
+ return;
923
+ }
924
+ submitBtn.disabled = true;
925
+ cancelBtn.disabled = true;
926
+ submitBtn.textContent = 'Creating\u2026';
927
+ submitBtn.style.opacity = '0.7';
928
+ statusEl.style.display = 'block';
929
+ statusEl.style.color = '#737373';
930
+ statusEl.textContent = 'Submitting your feedback\u2026';
931
+
932
+ var payload = {
933
+ prompt: feedback,
934
+ pageUrl: window.location.href,
935
+ screenshot: screenshotDataUrl || undefined,
936
+ };
937
+
938
+ fetch('/via/preview/task', {
939
+ method: 'POST',
940
+ headers: { 'Content-Type': 'application/json' },
941
+ body: JSON.stringify(payload),
942
+ }).then(function(res) {
943
+ if (!res.ok) {
944
+ return res.json().catch(function() { return {}; }).then(function(e) {
945
+ throw new Error(e.error || 'HTTP ' + res.status);
946
+ });
947
+ }
948
+ return res.json();
949
+ }).then(function() {
950
+ /* Success state */
951
+ modal.innerHTML = '';
952
+ var successDiv = document.createElement('div');
953
+ successDiv.style.cssText = 'text-align:center;padding:32px 24px;';
954
+ var check = document.createElement('div');
955
+ 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;';
956
+ check.textContent = '\\u2713';
957
+ var h3 = document.createElement('h3');
958
+ h3.textContent = 'Task Created!';
959
+ h3.style.cssText = 'margin:0 0 8px;font-size:16px;font-weight:600;color:#171717;font-family:Geist,-apple-system,BlinkMacSystemFont,sans-serif;';
960
+ var p = document.createElement('p');
961
+ p.textContent = 'Your feedback has been submitted successfully.';
962
+ p.style.cssText = 'margin:0;color:#737373;font-size:14px;font-family:Geist,-apple-system,BlinkMacSystemFont,sans-serif;';
963
+ successDiv.appendChild(check);
964
+ successDiv.appendChild(h3);
965
+ successDiv.appendChild(p);
966
+ modal.appendChild(successDiv);
967
+ setTimeout(function() { overlay.remove(); }, 2500);
968
+ }).catch(function(err) {
969
+ submitBtn.disabled = false;
970
+ cancelBtn.disabled = false;
971
+ submitBtn.textContent = 'Create Task';
972
+ submitBtn.style.opacity = '1';
973
+ statusEl.style.color = '#ef4444';
974
+ statusEl.textContent = 'Error: ' + (err.message || 'Failed to submit. Please try again.');
975
+ });
976
+ });
977
+
978
+ btnRow.appendChild(cancelBtn);
979
+ btnRow.appendChild(submitBtn);
980
+ modal.appendChild(btnRow);
981
+ overlay.appendChild(modal);
982
+
983
+ overlay.addEventListener('click', function(e) {
984
+ if (e.target === overlay) overlay.remove();
985
+ });
986
+ var escHandler = function(e) {
987
+ if (e.key === 'Escape') { overlay.remove(); document.removeEventListener('keydown', escHandler); }
988
+ };
989
+ document.addEventListener('keydown', escHandler);
990
+
991
+ document.body.appendChild(overlay);
992
+ setTimeout(function() { textarea.focus(); }, 50);
993
+ }
994
+ })();
995
+ `
996
+ );
997
+ }
800
998
  function buildClientScript(opts) {
801
999
  const pos = opts.position;
802
1000
  const pw = opts.panelWidth;
@@ -4334,7 +4532,9 @@ function registerFileRoutes(server, opts) {
4334
4532
  // src/inject.ts
4335
4533
  var SCRIPT_TAG = '<script src="/via/client.js" defer></script>';
4336
4534
  var MARKER = "viagen-toggle";
4337
- function createInjectionMiddleware() {
4535
+ var PREVIEW_SCRIPT_TAG = '<script src="/via/preview.js" defer></script>';
4536
+ var PREVIEW_MARKER = "viagen-preview-btn";
4537
+ function createScriptInjectionMiddleware(scriptTag, marker) {
4338
4538
  return function injectMiddleware(req, res, next) {
4339
4539
  if (req.method !== "GET" && req.method !== "HEAD") {
4340
4540
  return next();
@@ -4359,7 +4559,7 @@ function createInjectionMiddleware() {
4359
4559
  if (!isHtmlResponse()) return chunk;
4360
4560
  const str = typeof chunk === "string" ? chunk : Buffer.isBuffer(chunk) ? chunk.toString("utf-8") : null;
4361
4561
  if (!str) return chunk;
4362
- if (str.includes(MARKER) || str.includes("/via/client.js")) {
4562
+ if (str.includes(marker) || str.includes(scriptTag)) {
4363
4563
  injected = true;
4364
4564
  return chunk;
4365
4565
  }
@@ -4370,7 +4570,7 @@ function createInjectionMiddleware() {
4370
4570
  if (!res.headersSent) {
4371
4571
  res.removeHeader("content-length");
4372
4572
  }
4373
- const result = str.slice(0, idx) + SCRIPT_TAG + str.slice(idx);
4573
+ const result = str.slice(0, idx) + scriptTag + str.slice(idx);
4374
4574
  return typeof chunk === "string" ? result : Buffer.from(result, "utf-8");
4375
4575
  }
4376
4576
  res.write = function(chunk, ...args) {
@@ -4390,6 +4590,12 @@ function createInjectionMiddleware() {
4390
4590
  next();
4391
4591
  };
4392
4592
  }
4593
+ function createInjectionMiddleware() {
4594
+ return createScriptInjectionMiddleware(SCRIPT_TAG, MARKER);
4595
+ }
4596
+ function createPreviewInjectionMiddleware() {
4597
+ return createScriptInjectionMiddleware(PREVIEW_SCRIPT_TAG, PREVIEW_MARKER);
4598
+ }
4393
4599
 
4394
4600
  // src/git.ts
4395
4601
  import { readFileSync as readFileSync3 } from "fs";
@@ -18433,17 +18639,14 @@ function date4(params) {
18433
18639
  // node_modules/zod/v4/classic/external.js
18434
18640
  config(en_default());
18435
18641
 
18436
- // src/viagen-tools.ts
18642
+ // src/tools.ts
18437
18643
  import {
18438
18644
  createSdkMcpServer,
18439
18645
  tool
18440
18646
  } from "@anthropic-ai/claude-agent-sdk";
18441
- import {
18442
- listTasks,
18443
- getTask,
18444
- createTask
18445
- } from "viagen-sdk/sandbox";
18446
18647
  function createViagenTools(config2) {
18648
+ const { client, projectId } = config2;
18649
+ const taskId = process.env["VIAGEN_TASK_ID"];
18447
18650
  const tools = [
18448
18651
  tool(
18449
18652
  "viagen_update_task",
@@ -18459,112 +18662,94 @@ function createViagenTools(config2) {
18459
18662
  prReviewStatus: external_exports.string().optional().describe("PR review outcome \u2014 e.g. 'pass', 'flag', or 'fail'.")
18460
18663
  },
18461
18664
  async (args) => {
18462
- const taskId = args.taskId || process.env["VIAGEN_TASK_ID"];
18463
- if (!taskId) {
18665
+ const id = args.taskId || taskId;
18666
+ if (!id) {
18464
18667
  return {
18465
18668
  content: [{ type: "text", text: "Error: No taskId provided and VIAGEN_TASK_ID is not set." }]
18466
18669
  };
18467
18670
  }
18468
- const callbackUrl = process.env["VIAGEN_CALLBACK_URL"];
18469
- const authToken = process.env["VIAGEN_AUTH_TOKEN"];
18470
18671
  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,
18672
+ try {
18673
+ await client.tasks.update(projectId, id, {
18479
18674
  ...internalStatus && { status: internalStatus },
18480
18675
  ...args.prUrl && { prUrl: args.prUrl },
18481
18676
  result: args.result,
18482
18677
  ...args.inputTokens != null && { inputTokens: args.inputTokens },
18483
18678
  ...args.outputTokens != null && { outputTokens: args.outputTokens },
18484
- ...args.costUsd != null && { costUsd: args.costUsd },
18485
18679
  ...args.prReviewStatus && { prReviewStatus: args.prReviewStatus }
18486
- })
18487
- });
18488
- if (!res.ok) {
18489
- const text = await res.text().catch(() => "");
18680
+ });
18490
18681
  return {
18491
- content: [{ type: "text", text: `Error updating task (${res.status}): ${text}` }]
18682
+ content: [{ type: "text", text: `Task ${id} updated.${args.status ? ` Status: '${args.status}'.` : ""}${args.prReviewStatus ? ` PR review: '${args.prReviewStatus}'.` : ""}` }]
18683
+ };
18684
+ } catch (err) {
18685
+ const message = err instanceof Error ? err.message : "Unknown error";
18686
+ return {
18687
+ content: [{ type: "text", text: `Error updating task: ${message}` }]
18492
18688
  };
18493
18689
  }
18690
+ }
18691
+ ),
18692
+ tool(
18693
+ "viagen_list_tasks",
18694
+ "List tasks in the current project. Optionally filter by status.",
18695
+ {
18696
+ status: external_exports.enum(["ready", "running", "validating", "completed", "timed_out"]).optional().describe("Filter tasks by status.")
18697
+ },
18698
+ async (args) => {
18699
+ const tasks = await client.tasks.list(projectId, args.status);
18700
+ return {
18701
+ content: [
18702
+ {
18703
+ type: "text",
18704
+ text: JSON.stringify(tasks, null, 2)
18705
+ }
18706
+ ]
18707
+ };
18708
+ }
18709
+ ),
18710
+ tool(
18711
+ "viagen_get_task",
18712
+ "Get full details of a specific task by ID.",
18713
+ {
18714
+ taskId: external_exports.string().describe("The task ID to retrieve.")
18715
+ },
18716
+ async (args) => {
18717
+ const task = await client.tasks.get(projectId, args.taskId);
18718
+ return {
18719
+ content: [
18720
+ {
18721
+ type: "text",
18722
+ text: JSON.stringify(task, null, 2)
18723
+ }
18724
+ ]
18725
+ };
18726
+ }
18727
+ ),
18728
+ tool(
18729
+ "viagen_create_task",
18730
+ "Create a new task in the current project. Use this to create follow-up work.",
18731
+ {
18732
+ prompt: external_exports.string().describe("The task prompt / instructions."),
18733
+ branch: external_exports.string().optional().describe("Git branch name for the task."),
18734
+ type: external_exports.enum(["task", "plan"]).optional().describe("Task type: 'task' for code changes, 'plan' for implementation plans.")
18735
+ },
18736
+ async (args) => {
18737
+ const task = await client.tasks.create(projectId, {
18738
+ prompt: args.prompt,
18739
+ branch: args.branch,
18740
+ type: args.type
18741
+ });
18494
18742
  return {
18495
- content: [{ type: "text", text: `Task ${taskId} updated.${args.status ? ` Status: '${args.status}'.` : ""}${args.prReviewStatus ? ` PR review: '${args.prReviewStatus}'.` : ""}` }]
18743
+ content: [
18744
+ {
18745
+ type: "text",
18746
+ text: JSON.stringify(task, null, 2)
18747
+ }
18748
+ ]
18496
18749
  };
18497
18750
  }
18498
18751
  )
18499
18752
  ];
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
18753
  return createSdkMcpServer({ name: "viagen", tools });
18569
18754
  }
18570
18755
  var PLAN_MODE_DISALLOWED_TOOLS = ["Edit", "NotebookEdit"];
@@ -18604,6 +18789,9 @@ You have access to viagen platform tools for task management:
18604
18789
  Use these to understand project context and create follow-up work when appropriate.
18605
18790
  `;
18606
18791
 
18792
+ // src/index.ts
18793
+ import { createViagen } from "viagen-sdk";
18794
+
18607
18795
  // src/sandbox.ts
18608
18796
  import { randomUUID } from "crypto";
18609
18797
  import { readFileSync as readFileSync4, readdirSync as readdirSync2 } from "fs";
@@ -18868,6 +19056,7 @@ function viagen(options) {
18868
19056
  ui: options?.ui ?? true
18869
19057
  };
18870
19058
  let env;
19059
+ let previewEnabled = false;
18871
19060
  let projectRoot;
18872
19061
  let lastError = null;
18873
19062
  let promptSent = false;
@@ -18877,12 +19066,13 @@ function viagen(options) {
18877
19066
  name: "viagen",
18878
19067
  config(_, { mode }) {
18879
19068
  const e = loadEnv(mode, process.cwd(), "");
18880
- if (e["VIAGEN_AUTH_TOKEN"]) {
19069
+ if (e["VIAGEN_AUTH_TOKEN"] || e["VIAGEN_USER_TOKEN"]) {
18881
19070
  return { server: { host: true, allowedHosts: true } };
18882
19071
  }
18883
19072
  },
18884
19073
  configResolved(config2) {
18885
19074
  env = loadEnv(config2.mode, config2.envDir ?? config2.root, "");
19075
+ previewEnabled = env["VIAGEN_PREVIEW"] === "true";
18886
19076
  projectRoot = config2.root;
18887
19077
  const debugEnabled = options?.debug ?? env["VIAGEN_DEBUG"] === "1";
18888
19078
  setDebug(debugEnabled);
@@ -18893,6 +19083,7 @@ function viagen(options) {
18893
19083
  debug("init", `CLAUDE_ACCESS_TOKEN: ${env["CLAUDE_ACCESS_TOKEN"] ? "set" : "NOT SET"}`);
18894
19084
  debug("init", `GITHUB_TOKEN: ${env["GITHUB_TOKEN"] ? "set" : "NOT SET"}`);
18895
19085
  debug("init", `VIAGEN_AUTH_TOKEN: ${env["VIAGEN_AUTH_TOKEN"] ? "set" : "NOT SET"}`);
19086
+ debug("init", `VIAGEN_USER_TOKEN: ${env["VIAGEN_USER_TOKEN"] ? "set" : "NOT SET"}`);
18896
19087
  debug("init", `VIAGEN_MODEL: ${env["VIAGEN_MODEL"] || "(not set)"}`);
18897
19088
  debug("init", `VIAGEN_PROMPT: ${env["VIAGEN_PROMPT"] ? `"${env["VIAGEN_PROMPT"].slice(0, 80)}..."` : "(not set)"}`);
18898
19089
  debug("init", `VIAGEN_TASK_ID: ${env["VIAGEN_TASK_ID"] || "(not set)"}`);
@@ -18911,21 +19102,30 @@ function viagen(options) {
18911
19102
  );
18912
19103
  },
18913
19104
  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
- {
19105
+ const tags = [];
19106
+ if (opts.ui) {
19107
+ const url2 = new URL(ctx.originalUrl || ctx.path, "http://localhost");
19108
+ const isEmbed = url2.searchParams.has("_viagen_embed");
19109
+ if (!isEmbed || opts.overlay) {
19110
+ tags.push({
19111
+ tag: "script",
19112
+ children: buildClientScript({
19113
+ position: opts.position,
19114
+ panelWidth: opts.panelWidth,
19115
+ overlay: opts.overlay
19116
+ }),
19117
+ injectTo: "body"
19118
+ });
19119
+ }
19120
+ }
19121
+ if (previewEnabled) {
19122
+ tags.push({
18920
19123
  tag: "script",
18921
- children: buildClientScript({
18922
- position: opts.position,
18923
- panelWidth: opts.panelWidth,
18924
- overlay: opts.overlay
18925
- }),
19124
+ children: buildPreviewScript(),
18926
19125
  injectTo: "body"
18927
- }
18928
- ];
19126
+ });
19127
+ }
19128
+ return tags;
18929
19129
  },
18930
19130
  configureServer(server) {
18931
19131
  debug("server", "configureServer starting");
@@ -18968,6 +19168,14 @@ ${payload.err.frame || ""}`
18968
19168
  } else {
18969
19169
  debug("server", "auth middleware DISABLED (no VIAGEN_AUTH_TOKEN)");
18970
19170
  }
19171
+ const platformToken = env["VIAGEN_USER_TOKEN"] || env["VIAGEN_AUTH_TOKEN"];
19172
+ const platformUrl = env["VIAGEN_PLATFORM_URL"] || "https://app.viagen.dev";
19173
+ const projectId = env["VIAGEN_PROJECT_ID"];
19174
+ let viagenClient = null;
19175
+ if (platformToken) {
19176
+ viagenClient = createViagen({ token: platformToken, baseUrl: platformUrl });
19177
+ debug("server", `platform client created (baseUrl: ${platformUrl})`);
19178
+ }
18971
19179
  const hasEditor = !!(options?.editable && options.editable.length > 0);
18972
19180
  const clientJs = buildClientScript({
18973
19181
  position: opts.position,
@@ -18990,18 +19198,80 @@ ${payload.err.frame || ""}`
18990
19198
  res.setHeader("Content-Type", "text/html");
18991
19199
  res.end(buildIframeHtml({ panelWidth: opts.panelWidth }));
18992
19200
  });
19201
+ if (previewEnabled) {
19202
+ const previewJs = buildPreviewScript();
19203
+ server.middlewares.use("/via/preview.js", (_req, res) => {
19204
+ res.setHeader("Content-Type", "application/javascript");
19205
+ res.end(previewJs);
19206
+ });
19207
+ server.middlewares.use("/via/preview/task", (req, res) => {
19208
+ if (req.method !== "POST") {
19209
+ res.writeHead(405, { "Content-Type": "application/json" });
19210
+ res.end(JSON.stringify({ error: "Method not allowed" }));
19211
+ return;
19212
+ }
19213
+ if (!viagenClient || !projectId) {
19214
+ res.writeHead(503, { "Content-Type": "application/json" });
19215
+ res.end(JSON.stringify({ error: "Task creation not configured: missing platform credentials or VIAGEN_PROJECT_ID" }));
19216
+ return;
19217
+ }
19218
+ let body = "";
19219
+ req.on("data", (chunk) => {
19220
+ body += chunk.toString();
19221
+ });
19222
+ req.on("end", async () => {
19223
+ try {
19224
+ const data = JSON.parse(body);
19225
+ const { prompt, pageUrl, screenshot } = data;
19226
+ if (!prompt || typeof prompt !== "string") {
19227
+ res.writeHead(400, { "Content-Type": "application/json" });
19228
+ res.end(JSON.stringify({ error: "prompt is required" }));
19229
+ return;
19230
+ }
19231
+ const parts = [prompt.trim()];
19232
+ if (pageUrl) parts.push(`
19233
+ Page URL: ${pageUrl}`);
19234
+ if (screenshot) parts.push("\n[Screenshot attached]");
19235
+ parts.push("\n\n[Submitted via viagen preview feedback]");
19236
+ const fullPrompt = parts.join("");
19237
+ const task = await viagenClient.tasks.create(projectId, { prompt: fullPrompt, type: "task" });
19238
+ if (screenshot && task.id) {
19239
+ try {
19240
+ const [header, b64] = screenshot.split(",");
19241
+ const mime = header.match(/:(.*?);/)?.[1] || "image/jpeg";
19242
+ const binary = Buffer.from(b64, "base64");
19243
+ const blob = new Blob([binary], { type: mime });
19244
+ const ext = mime === "image/png" ? "png" : "jpeg";
19245
+ await viagenClient.tasks.addAttachment(projectId, task.id, blob, `screenshot.${ext}`);
19246
+ debug("preview", `screenshot attached to task ${task.id}`);
19247
+ } catch (attachErr) {
19248
+ debug("preview", `screenshot attachment failed (non-fatal): ${attachErr}`);
19249
+ }
19250
+ }
19251
+ res.writeHead(200, { "Content-Type": "application/json" });
19252
+ res.end(JSON.stringify({ task }));
19253
+ } catch (err) {
19254
+ const message = err instanceof Error ? err.message : "Internal error";
19255
+ debug("preview", `preview/task error: ${message}`);
19256
+ res.writeHead(500, { "Content-Type": "application/json" });
19257
+ res.end(JSON.stringify({ error: message }));
19258
+ }
19259
+ });
19260
+ });
19261
+ }
18993
19262
  registerHealthRoutes(server, env, {
18994
19263
  get: () => lastError
18995
19264
  });
18996
19265
  const resolvedModel = env["VIAGEN_MODEL"] || opts.model;
18997
19266
  debug("server", `creating ChatSession (model: ${resolvedModel})`);
18998
19267
  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
- );
19268
+ const hasPlatformContext = !!(viagenClient && projectId);
19269
+ if (hasPlatformContext) {
19270
+ debug("server", "creating viagen MCP tools (platform connected)");
19271
+ const viagenMcp = createViagenTools({
19272
+ client: viagenClient,
19273
+ projectId
19274
+ });
19005
19275
  mcpServers = { [viagenMcp.name]: viagenMcp };
19006
19276
  }
19007
19277
  const isPlanMode = env["VIAGEN_TASK_TYPE"] === "plan";
@@ -19009,7 +19279,7 @@ ${payload.err.frame || ""}`
19009
19279
  if (isPlanMode) {
19010
19280
  debug("server", "plan mode active \u2014 restricting tools");
19011
19281
  systemPrompt = PLAN_SYSTEM_PROMPT;
19012
- } else if (hasSandboxContext && env["VIAGEN_PROJECT_ID"]) {
19282
+ } else if (hasPlatformContext) {
19013
19283
  systemPrompt = (systemPrompt || "") + TASK_TOOLS_PROMPT;
19014
19284
  }
19015
19285
  const chatSession = new ChatSession({
@@ -19025,7 +19295,11 @@ ${payload.err.frame || ""}`
19025
19295
  } : {}
19026
19296
  });
19027
19297
  debug("server", "registering chat routes");
19028
- registerChatRoutes(server, chatSession, { env });
19298
+ registerChatRoutes(server, chatSession, {
19299
+ env,
19300
+ viagenClient: viagenClient ?? void 0,
19301
+ projectId
19302
+ });
19029
19303
  if (hasEditor) {
19030
19304
  registerFileRoutes(server, {
19031
19305
  editable: options.editable,
@@ -19043,30 +19317,26 @@ ${payload.err.frame || ""}`
19043
19317
  if (event.type === "done") {
19044
19318
  debug("server", "auto-prompt completed");
19045
19319
  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
- })
19320
+ const currentTaskId = env["VIAGEN_TASK_ID"];
19321
+ if (viagenClient && projectId && currentTaskId && (event.inputTokens || event.outputTokens)) {
19322
+ viagenClient.tasks.update(projectId, currentTaskId, {
19323
+ ...event.inputTokens != null && { inputTokens: event.inputTokens },
19324
+ ...event.outputTokens != null && { outputTokens: event.outputTokens }
19060
19325
  }).catch((err) => {
19061
- debug("server", `usage callback failed: ${err}`);
19326
+ debug("server", `usage report failed: ${err}`);
19062
19327
  });
19063
19328
  }
19064
19329
  }
19065
19330
  });
19066
19331
  }
19067
- if (opts.ui) {
19332
+ if (opts.ui || previewEnabled) {
19068
19333
  return () => {
19069
- server.middlewares.use(createInjectionMiddleware());
19334
+ if (opts.ui) {
19335
+ server.middlewares.use(createInjectionMiddleware());
19336
+ }
19337
+ if (previewEnabled) {
19338
+ server.middlewares.use(createPreviewInjectionMiddleware());
19339
+ }
19070
19340
  };
19071
19341
  }
19072
19342
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "viagen",
3
- "version": "0.0.53",
3
+ "version": "0.0.54",
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",
@@ -44,7 +44,7 @@
44
44
  "@vercel/sandbox": "^1",
45
45
  "lucide-react": "^0.564.0",
46
46
  "simple-git": "^3.31.1",
47
- "viagen-sdk": "^0.0.11"
47
+ "viagen-sdk": "^0.0.12"
48
48
  },
49
49
  "license": "MIT",
50
50
  "repository": {