viagen 0.0.45 → 0.0.46

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 +164 -15
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ var __export = (target, all) => {
6
6
 
7
7
  // src/index.ts
8
8
  import { execSync as execSync2 } from "child_process";
9
- import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync4 } from "fs";
9
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
10
10
  import { join as join6 } from "path";
11
11
  import { loadEnv } from "vite";
12
12
 
@@ -110,7 +110,7 @@ function registerHealthRoutes(server, env, errorRef) {
110
110
  );
111
111
  }
112
112
  });
113
- const currentVersion = true ? "0.0.45" : "0.0.0";
113
+ const currentVersion = true ? "0.0.46" : "0.0.0";
114
114
  debug("health", `version resolved: ${currentVersion}`);
115
115
  let versionCache = null;
116
116
  server.middlewares.use("/via/version", (_req, res) => {
@@ -2122,7 +2122,8 @@ function buildUiHtml(opts) {
2122
2122
  font-family: inherit;
2123
2123
  outline: none;
2124
2124
  resize: none;
2125
- height: 24px;
2125
+ height: auto;
2126
+ min-height: 20px;
2126
2127
  overflow: hidden;
2127
2128
  line-height: 1.5;
2128
2129
  }
@@ -2151,7 +2152,45 @@ function buildUiHtml(opts) {
2151
2152
  .send-btn:hover { background: #e5e5e5; color: #525252; }
2152
2153
  .send-btn.active { background: #171717; color: #ffffff; border-color: #171717; }
2153
2154
  .send-btn.active:hover { background: #404040; border-color: #404040; }
2155
+ .attach-btn {
2156
+ padding: 6px 6px;
2157
+ background: transparent;
2158
+ color: #a3a3a3;
2159
+ border: none;
2160
+ border-radius: 8px;
2161
+ cursor: pointer;
2162
+ display: flex;
2163
+ align-items: center;
2164
+ justify-content: center;
2165
+ transition: color 0.15s, background 0.15s;
2166
+ margin-right: 2px;
2167
+ }
2168
+ .attach-btn:hover { color: #525252; background: #f5f5f5; }
2154
2169
  .send-btn:disabled { opacity: 0.3; cursor: not-allowed; }
2170
+ .send-btn.stop { background: #ef4444; color: #ffffff; border-color: #ef4444; opacity: 1; cursor: pointer; }
2171
+ .send-btn.stop:hover { background: #dc2626; border-color: #dc2626; }
2172
+ .drop-overlay {
2173
+ display: none;
2174
+ position: absolute;
2175
+ inset: 0;
2176
+ background: rgba(37,99,235,0.08);
2177
+ border: 2px dashed #2563eb;
2178
+ border-radius: 12px;
2179
+ z-index: 100;
2180
+ align-items: center;
2181
+ justify-content: center;
2182
+ pointer-events: none;
2183
+ }
2184
+ .drop-overlay.visible { display: flex; }
2185
+ .drop-overlay span {
2186
+ font-size: 13px;
2187
+ font-weight: 600;
2188
+ color: #2563eb;
2189
+ background: #ffffff;
2190
+ padding: 8px 16px;
2191
+ border-radius: 8px;
2192
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
2193
+ }
2155
2194
  .status-bar {
2156
2195
  display: none;
2157
2196
  align-items: center;
@@ -2438,7 +2477,8 @@ function buildUiHtml(opts) {
2438
2477
  ${hasEditor ? '<button class="tab" data-tab="files"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>Files</button>' : ""}
2439
2478
  <button class="tab" data-tab="logs"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><line x1="8" x2="21" y1="6" y2="6"/><line x1="8" x2="21" y1="12" y2="12"/><line x1="8" x2="21" y1="18" y2="18"/><line x1="3" x2="3.01" y1="6" y2="6"/><line x1="3" x2="3.01" y1="12" y2="12"/><line x1="3" x2="3.01" y1="18" y2="18"/></svg>Logs</button>
2440
2479
  </div>` : ""}
2441
- <div id="chat-view" style="display:flex;flex-direction:column;flex:1;overflow:hidden;">
2480
+ <div id="chat-view" style="display:flex;flex-direction:column;flex:1;overflow:hidden;position:relative;">
2481
+ <div class="drop-overlay" id="drop-overlay"><span>Drop files to upload</span></div>
2442
2482
  <div class="setup-banner" id="setup-banner"></div>
2443
2483
 
2444
2484
  <div class="activity-bar" id="activity-bar"></div>
@@ -2448,6 +2488,8 @@ function buildUiHtml(opts) {
2448
2488
  <div class="input-wrap" id="input-wrap">
2449
2489
  <textarea id="input" rows="1" placeholder="What do you want to build?" autofocus></textarea>
2450
2490
  <div class="input-bottom">
2491
+ <input type="file" id="file-input" multiple style="display:none;">
2492
+ <button class="attach-btn" id="attach-btn" title="Upload files"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg></button>
2451
2493
  <button class="send-btn" id="send-btn"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="19" x2="12" y2="5"></line><polyline points="5 12 12 5 19 12"></polyline></svg></button>
2452
2494
  </div>
2453
2495
  </div>
@@ -2545,7 +2587,7 @@ function buildUiHtml(opts) {
2545
2587
 
2546
2588
  function scrollToBottom() {
2547
2589
  requestAnimationFrame(function() {
2548
- scrollToBottom();
2590
+ messagesEl.scrollTop = messagesEl.scrollHeight;
2549
2591
  });
2550
2592
  }
2551
2593
 
@@ -3202,23 +3244,35 @@ function buildUiHtml(opts) {
3202
3244
  }
3203
3245
  inputEl.addEventListener('input', function() { autoResize(); updateSendActive(); });
3204
3246
 
3247
+ var currentAbort = null;
3205
3248
  function setStreaming(v) {
3206
3249
  isStreaming = v;
3207
3250
  inputEl.disabled = v;
3208
3251
  document.getElementById('input-wrap').classList.toggle('disabled', v);
3209
- sendBtn.disabled = v;
3210
- sendBtn.innerHTML = v ? '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="animation:spin 1s linear infinite"><path d="M21 12a9 9 0 1 1-6.219-8.56"></path></svg>' : '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="19" x2="12" y2="5"></line><polyline points="5 12 12 5 19 12"></polyline></svg>';
3252
+ if (v) {
3253
+ sendBtn.disabled = false;
3254
+ sendBtn.classList.add('stop');
3255
+ sendBtn.classList.remove('active');
3256
+ sendBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="none"><rect x="6" y="6" width="12" height="12" rx="2"></rect></svg>';
3257
+ } else {
3258
+ sendBtn.classList.remove('stop');
3259
+ sendBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="19" x2="12" y2="5"></line><polyline points="5 12 12 5 19 12"></polyline></svg>';
3260
+ currentAbort = null;
3261
+ }
3211
3262
  updateSendActive();
3212
3263
  if (v) stopHistoryPolling();
3213
3264
  else startHistoryPolling();
3214
3265
  }
3215
3266
 
3216
3267
  async function sendRaw(text) {
3268
+ var abort = new AbortController();
3269
+ currentAbort = abort;
3217
3270
  try {
3218
3271
  var res = await fetch('/via/chat', {
3219
3272
  method: 'POST',
3220
3273
  headers: { 'Content-Type': 'application/json' },
3221
3274
  body: JSON.stringify({ message: text }),
3275
+ signal: abort.signal,
3222
3276
  });
3223
3277
 
3224
3278
  var reader = res.body.getReader();
@@ -3255,13 +3309,17 @@ function buildUiHtml(opts) {
3255
3309
  }
3256
3310
  }
3257
3311
  } catch (e) {
3258
- if (!unloading) addErrorBlock('Connection failed');
3312
+ if (abort.signal.aborted) {
3313
+ // User cancelled \u2014 not an error
3314
+ } else if (!unloading) {
3315
+ addErrorBlock('Connection failed');
3316
+ }
3259
3317
  }
3260
3318
 
3261
3319
  closeToolGroup();
3262
3320
  closeAllTaskGroups('completed');
3263
3321
  hideActivity(lastUsage);
3264
- playDoneSound();
3322
+ if (!abort.signal.aborted) playDoneSound();
3265
3323
  historyTimestamp = Date.now();
3266
3324
  setStreaming(false);
3267
3325
  markCommittedFiles();
@@ -3288,7 +3346,13 @@ function buildUiHtml(opts) {
3288
3346
  await sendRaw(text);
3289
3347
  }
3290
3348
 
3291
- sendBtn.addEventListener('click', send);
3349
+ sendBtn.addEventListener('click', function() {
3350
+ if (isStreaming && currentAbort) {
3351
+ currentAbort.abort();
3352
+ return;
3353
+ }
3354
+ send();
3355
+ });
3292
3356
  inputEl.addEventListener('keydown', function (e) {
3293
3357
  if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); }
3294
3358
  });
@@ -3300,6 +3364,79 @@ function buildUiHtml(opts) {
3300
3364
  currentTextSpan = null;
3301
3365
  inputEl.focus();
3302
3366
  });
3367
+ // \u2500\u2500 Drag & drop file upload \u2500\u2500
3368
+ var chatView = document.getElementById('chat-view');
3369
+ var dropOverlay = document.getElementById('drop-overlay');
3370
+ var dragCounter = 0;
3371
+ chatView.addEventListener('dragenter', function(e) {
3372
+ e.preventDefault();
3373
+ dragCounter++;
3374
+ dropOverlay.classList.add('visible');
3375
+ });
3376
+ chatView.addEventListener('dragleave', function(e) {
3377
+ e.preventDefault();
3378
+ dragCounter--;
3379
+ if (dragCounter <= 0) { dragCounter = 0; dropOverlay.classList.remove('visible'); }
3380
+ });
3381
+ chatView.addEventListener('dragover', function(e) { e.preventDefault(); });
3382
+ function readFileAsBase64(file) {
3383
+ return new Promise(function(resolve) {
3384
+ var reader = new FileReader();
3385
+ reader.onload = function() {
3386
+ var result = reader.result;
3387
+ resolve(result.split(',')[1] || result);
3388
+ };
3389
+ reader.readAsDataURL(file);
3390
+ });
3391
+ }
3392
+ var MAX_UPLOAD_SIZE = 10 * 1024 * 1024; // 10MB
3393
+ async function uploadFiles(files) {
3394
+ if (!files || !files.length) return;
3395
+ var uploaded = [];
3396
+ for (var i = 0; i < files.length; i++) {
3397
+ var file = files[i];
3398
+ if (file.size > MAX_UPLOAD_SIZE) {
3399
+ addErrorBlock(file.name + ' is too large (max 10MB)');
3400
+ continue;
3401
+ }
3402
+ var isText = file.type.startsWith('text/') || /\\.(json|md|txt|csv|js|ts|jsx|tsx|css|html|xml|yaml|yml|toml|sh|py|rb|go|rs|sql|env|log|cfg|ini|conf)$/i.test(file.name);
3403
+ try {
3404
+ var payload;
3405
+ if (isText) {
3406
+ payload = { path: '.viagen/uploads/' + file.name, content: await file.text() };
3407
+ } else {
3408
+ payload = { path: '.viagen/uploads/' + file.name, content: await readFileAsBase64(file), encoding: 'base64' };
3409
+ }
3410
+ await fetch('/via/file', {
3411
+ method: 'POST',
3412
+ headers: { 'Content-Type': 'application/json' },
3413
+ body: JSON.stringify(payload),
3414
+ });
3415
+ uploaded.push(file.name);
3416
+ } catch (err) {
3417
+ addErrorBlock('Failed to upload ' + file.name);
3418
+ }
3419
+ }
3420
+ if (uploaded.length > 0) {
3421
+ var msg = uploaded.length === 1
3422
+ ? 'I just uploaded a file: .viagen/uploads/' + uploaded[0] + '. Take a look and let me know what you think.'
3423
+ : 'I just uploaded ' + uploaded.length + ' files to .viagen/uploads/: ' + uploaded.join(', ') + '. Take a look and let me know what you think.';
3424
+ inputEl.value = msg;
3425
+ send();
3426
+ }
3427
+ }
3428
+ chatView.addEventListener('drop', async function(e) {
3429
+ e.preventDefault();
3430
+ dragCounter = 0;
3431
+ dropOverlay.classList.remove('visible');
3432
+ uploadFiles(e.dataTransfer ? e.dataTransfer.files : []);
3433
+ });
3434
+
3435
+ // \u2500\u2500 Paperclip file picker \u2500\u2500
3436
+ var fileInput = document.getElementById('file-input');
3437
+ document.getElementById('attach-btn').addEventListener('click', function() { fileInput.click(); });
3438
+ fileInput.addEventListener('change', function() { uploadFiles(fileInput.files); fileInput.value = ''; });
3439
+
3303
3440
  // \u2500\u2500 View mode detection \u2500\u2500
3304
3441
  var viewMode = 'overlay';
3305
3442
  if (window.self === window.top) viewMode = 'standalone';
@@ -3965,8 +4102,14 @@ function createAuthMiddleware(token) {
3965
4102
  }
3966
4103
 
3967
4104
  // src/files.ts
3968
- import { readdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync3, statSync } from "fs";
3969
- import { join as join3, resolve, relative, basename } from "path";
4105
+ import {
4106
+ readdirSync,
4107
+ readFileSync as readFileSync2,
4108
+ writeFileSync as writeFileSync3,
4109
+ mkdirSync as mkdirSync2,
4110
+ statSync
4111
+ } from "fs";
4112
+ import { join as join3, resolve, relative, basename, dirname } from "path";
3970
4113
  function readBody2(req) {
3971
4114
  return new Promise((resolve3, reject) => {
3972
4115
  let body = "";
@@ -4133,14 +4276,20 @@ function registerFileRoutes(server, opts) {
4133
4276
  res.end(JSON.stringify({ error: "Missing path or content" }));
4134
4277
  return;
4135
4278
  }
4136
- if (!isPathAllowed(writePath, resolvedPatterns, opts.projectRoot)) {
4279
+ const isViagen = writePath.startsWith(".viagen/");
4280
+ if (!isViagen && !isPathAllowed(writePath, resolvedPatterns, opts.projectRoot)) {
4137
4281
  res.statusCode = 403;
4138
4282
  res.end(JSON.stringify({ error: "Path not in editable list" }));
4139
4283
  return;
4140
4284
  }
4141
4285
  const abs = resolve(opts.projectRoot, writePath);
4142
4286
  try {
4143
- writeFileSync3(abs, writeContent, "utf-8");
4287
+ mkdirSync2(dirname(abs), { recursive: true });
4288
+ if (body.encoding === "base64") {
4289
+ writeFileSync3(abs, Buffer.from(writeContent, "base64"));
4290
+ } else {
4291
+ writeFileSync3(abs, writeContent, "utf-8");
4292
+ }
4144
4293
  res.setHeader("Content-Type", "application/json");
4145
4294
  res.end(JSON.stringify({ status: "ok", path: writePath }));
4146
4295
  } catch (err) {
@@ -18621,7 +18770,7 @@ function viagen(options) {
18621
18770
  logBuffer.init(projectRoot);
18622
18771
  wrapLogger(config2.logger, logBuffer);
18623
18772
  const viagenDir = join6(projectRoot, ".viagen");
18624
- mkdirSync2(viagenDir, { recursive: true });
18773
+ mkdirSync3(viagenDir, { recursive: true });
18625
18774
  writeFileSync4(
18626
18775
  join6(viagenDir, "config.json"),
18627
18776
  JSON.stringify({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "viagen",
3
- "version": "0.0.45",
3
+ "version": "0.0.46",
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",