staklink 0.3.54 → 0.3.56

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.
@@ -1289,8 +1289,8 @@ var require_node = __commonJS({
1289
1289
  }
1290
1290
  break;
1291
1291
  case "FILE":
1292
- var fs12 = require("fs");
1293
- stream3 = new fs12.SyncWriteStream(fd2, { autoClose: false });
1292
+ var fs13 = require("fs");
1293
+ stream3 = new fs13.SyncWriteStream(fd2, { autoClose: false });
1294
1294
  stream3._type = "fs";
1295
1295
  break;
1296
1296
  case "PIPE":
@@ -17508,8 +17508,8 @@ var require_node2 = __commonJS({
17508
17508
  }
17509
17509
  break;
17510
17510
  case "FILE":
17511
- var fs12 = require("fs");
17512
- stream3 = new fs12.SyncWriteStream(fd2, { autoClose: false });
17511
+ var fs13 = require("fs");
17512
+ stream3 = new fs13.SyncWriteStream(fd2, { autoClose: false });
17513
17513
  stream3._type = "fs";
17514
17514
  break;
17515
17515
  case "PIPE":
@@ -18227,8 +18227,8 @@ var require_node3 = __commonJS({
18227
18227
  }
18228
18228
  break;
18229
18229
  case "FILE":
18230
- var fs12 = require("fs");
18231
- stream3 = new fs12.SyncWriteStream(fd2, { autoClose: false });
18230
+ var fs13 = require("fs");
18231
+ stream3 = new fs13.SyncWriteStream(fd2, { autoClose: false });
18232
18232
  stream3._type = "fs";
18233
18233
  break;
18234
18234
  case "PIPE":
@@ -19120,7 +19120,7 @@ var require_view = __commonJS({
19120
19120
  "use strict";
19121
19121
  var debug = require_src3()("express:view");
19122
19122
  var path11 = require("path");
19123
- var fs12 = require("fs");
19123
+ var fs13 = require("fs");
19124
19124
  var dirname2 = path11.dirname;
19125
19125
  var basename2 = path11.basename;
19126
19126
  var extname = path11.extname;
@@ -19186,7 +19186,7 @@ var require_view = __commonJS({
19186
19186
  function tryStat(path12) {
19187
19187
  debug('stat "%s"', path12);
19188
19188
  try {
19189
- return fs12.statSync(path12);
19189
+ return fs13.statSync(path12);
19190
19190
  } catch (e) {
19191
19191
  return void 0;
19192
19192
  }
@@ -19791,8 +19791,8 @@ var require_node4 = __commonJS({
19791
19791
  }
19792
19792
  break;
19793
19793
  case "FILE":
19794
- var fs12 = require("fs");
19795
- stream3 = new fs12.SyncWriteStream(fd2, { autoClose: false });
19794
+ var fs13 = require("fs");
19795
+ stream3 = new fs13.SyncWriteStream(fd2, { autoClose: false });
19796
19796
  stream3._type = "fs";
19797
19797
  break;
19798
19798
  case "PIPE":
@@ -19979,7 +19979,7 @@ var require_types = __commonJS({
19979
19979
  var require_mime = __commonJS({
19980
19980
  "node_modules/mime/mime.js"(exports2, module2) {
19981
19981
  var path11 = require("path");
19982
- var fs12 = require("fs");
19982
+ var fs13 = require("fs");
19983
19983
  function Mime() {
19984
19984
  this.types = /* @__PURE__ */ Object.create(null);
19985
19985
  this.extensions = /* @__PURE__ */ Object.create(null);
@@ -20000,7 +20000,7 @@ var require_mime = __commonJS({
20000
20000
  };
20001
20001
  Mime.prototype.load = function(file3) {
20002
20002
  this._loading = file3;
20003
- var map3 = {}, content = fs12.readFileSync(file3, "ascii"), lines = content.split(/[\r\n]+/);
20003
+ var map3 = {}, content = fs13.readFileSync(file3, "ascii"), lines = content.split(/[\r\n]+/);
20004
20004
  lines.forEach(function(line) {
20005
20005
  var fields = line.replace(/\s*#.*|^\s*|\s*$/g, "").split(/\s+/);
20006
20006
  map3[fields.shift()] = fields;
@@ -20238,7 +20238,7 @@ var require_send = __commonJS({
20238
20238
  var escapeHtml = require_escape_html();
20239
20239
  var etag = require_etag();
20240
20240
  var fresh = require_fresh();
20241
- var fs12 = require("fs");
20241
+ var fs13 = require("fs");
20242
20242
  var mime = require_mime();
20243
20243
  var ms = require_ms5();
20244
20244
  var onFinished = require_on_finished();
@@ -20571,7 +20571,7 @@ var require_send = __commonJS({
20571
20571
  var i = 0;
20572
20572
  var self = this;
20573
20573
  debug('stat "%s"', path12);
20574
- fs12.stat(path12, function onstat(err, stat4) {
20574
+ fs13.stat(path12, function onstat(err, stat4) {
20575
20575
  if (err && err.code === "ENOENT" && !extname(path12) && path12[path12.length - 1] !== sep2) {
20576
20576
  return next(err);
20577
20577
  }
@@ -20586,7 +20586,7 @@ var require_send = __commonJS({
20586
20586
  }
20587
20587
  var p = path12 + "." + self._extensions[i++];
20588
20588
  debug('stat "%s"', p);
20589
- fs12.stat(p, function(err2, stat4) {
20589
+ fs13.stat(p, function(err2, stat4) {
20590
20590
  if (err2) return next(err2);
20591
20591
  if (stat4.isDirectory()) return next();
20592
20592
  self.emit("file", p, stat4);
@@ -20604,7 +20604,7 @@ var require_send = __commonJS({
20604
20604
  }
20605
20605
  var p = join7(path12, self._index[i]);
20606
20606
  debug('stat "%s"', p);
20607
- fs12.stat(p, function(err2, stat4) {
20607
+ fs13.stat(p, function(err2, stat4) {
20608
20608
  if (err2) return next(err2);
20609
20609
  if (stat4.isDirectory()) return next();
20610
20610
  self.emit("file", p, stat4);
@@ -20616,7 +20616,7 @@ var require_send = __commonJS({
20616
20616
  SendStream.prototype.stream = function stream2(path12, options) {
20617
20617
  var self = this;
20618
20618
  var res = this.res;
20619
- var stream3 = fs12.createReadStream(path12, options);
20619
+ var stream3 = fs13.createReadStream(path12, options);
20620
20620
  this.emit("stream", stream3);
20621
20621
  stream3.pipe(res);
20622
20622
  function cleanup() {
@@ -24146,7 +24146,7 @@ var require_token_util = __commonJS({
24146
24146
  });
24147
24147
  module2.exports = __toCommonJS(token_util_exports);
24148
24148
  var path11 = __toESM2(require("path"));
24149
- var fs12 = __toESM2(require("fs"));
24149
+ var fs13 = __toESM2(require("fs"));
24150
24150
  var import_token_error = require_token_error();
24151
24151
  var import_token_io = require_token_io();
24152
24152
  function getVercelDataDir() {
@@ -24163,10 +24163,10 @@ var require_token_util = __commonJS({
24163
24163
  return null;
24164
24164
  }
24165
24165
  const tokenPath = path11.join(dataDir, "auth.json");
24166
- if (!fs12.existsSync(tokenPath)) {
24166
+ if (!fs13.existsSync(tokenPath)) {
24167
24167
  return null;
24168
24168
  }
24169
- const token = fs12.readFileSync(tokenPath, "utf8");
24169
+ const token = fs13.readFileSync(tokenPath, "utf8");
24170
24170
  if (!token) {
24171
24171
  return null;
24172
24172
  }
@@ -24208,10 +24208,10 @@ var require_token_util = __commonJS({
24208
24208
  }
24209
24209
  try {
24210
24210
  const prjPath = path11.join(dir, ".vercel", "project.json");
24211
- if (!fs12.existsSync(prjPath)) {
24211
+ if (!fs13.existsSync(prjPath)) {
24212
24212
  throw new import_token_error.VercelOidcTokenError("project.json not found");
24213
24213
  }
24214
- const prj = JSON.parse(fs12.readFileSync(prjPath, "utf8"));
24214
+ const prj = JSON.parse(fs13.readFileSync(prjPath, "utf8"));
24215
24215
  if (typeof prj.projectId !== "string" && typeof prj.orgId !== "string") {
24216
24216
  throw new TypeError("Expected a string-valued projectId property");
24217
24217
  }
@@ -24228,9 +24228,9 @@ var require_token_util = __commonJS({
24228
24228
  }
24229
24229
  const tokenPath = path11.join(dir, "com.vercel.token", `${projectId}.json`);
24230
24230
  const tokenJson = JSON.stringify(token);
24231
- fs12.mkdirSync(path11.dirname(tokenPath), { mode: 504, recursive: true });
24232
- fs12.writeFileSync(tokenPath, tokenJson);
24233
- fs12.chmodSync(tokenPath, 432);
24231
+ fs13.mkdirSync(path11.dirname(tokenPath), { mode: 504, recursive: true });
24232
+ fs13.writeFileSync(tokenPath, tokenJson);
24233
+ fs13.chmodSync(tokenPath, 432);
24234
24234
  return;
24235
24235
  } catch (e) {
24236
24236
  throw new import_token_error.VercelOidcTokenError(`Failed to save token`, e);
@@ -24243,10 +24243,10 @@ var require_token_util = __commonJS({
24243
24243
  return null;
24244
24244
  }
24245
24245
  const tokenPath = path11.join(dir, "com.vercel.token", `${projectId}.json`);
24246
- if (!fs12.existsSync(tokenPath)) {
24246
+ if (!fs13.existsSync(tokenPath)) {
24247
24247
  return null;
24248
24248
  }
24249
- const token = JSON.parse(fs12.readFileSync(tokenPath, "utf8"));
24249
+ const token = JSON.parse(fs13.readFileSync(tokenPath, "utf8"));
24250
24250
  assertVercelOidcTokenResponse(token);
24251
24251
  return token;
24252
24252
  } catch (e) {
@@ -38067,7 +38067,7 @@ var require_snapshot_utils = __commonJS({
38067
38067
  var require_snapshot_recorder = __commonJS({
38068
38068
  "node_modules/undici/lib/mock/snapshot-recorder.js"(exports2, module2) {
38069
38069
  "use strict";
38070
- var { writeFile: writeFile3, readFile: readFile5, mkdir: mkdir2 } = require("node:fs/promises");
38070
+ var { writeFile: writeFile3, readFile: readFile6, mkdir: mkdir2 } = require("node:fs/promises");
38071
38071
  var { dirname: dirname2, resolve: resolve3 } = require("node:path");
38072
38072
  var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = require("node:timers");
38073
38073
  var { InvalidArgumentError: InvalidArgumentError5, UndiciError } = require_errors();
@@ -38262,7 +38262,7 @@ var require_snapshot_recorder = __commonJS({
38262
38262
  throw new InvalidArgumentError5("Snapshot path is required");
38263
38263
  }
38264
38264
  try {
38265
- const data = await readFile5(resolve3(path11), "utf8");
38265
+ const data = await readFile6(resolve3(path11), "utf8");
38266
38266
  const parsed = JSON.parse(data);
38267
38267
  if (Array.isArray(parsed)) {
38268
38268
  this.#snapshots.clear();
@@ -54565,8 +54565,8 @@ var PathScurryBase = class {
54565
54565
  *
54566
54566
  * @internal
54567
54567
  */
54568
- constructor(cwd = process.cwd(), pathImpl, sep2, { nocase, childrenCacheSize = 16 * 1024, fs: fs12 = defaultFS } = {}) {
54569
- this.#fs = fsFromOption(fs12);
54568
+ constructor(cwd = process.cwd(), pathImpl, sep2, { nocase, childrenCacheSize = 16 * 1024, fs: fs13 = defaultFS } = {}) {
54569
+ this.#fs = fsFromOption(fs13);
54570
54570
  if (cwd instanceof URL || cwd.startsWith("file://")) {
54571
54571
  cwd = (0, import_node_url.fileURLToPath)(cwd);
54572
54572
  }
@@ -55124,8 +55124,8 @@ var PathScurryWin32 = class extends PathScurryBase {
55124
55124
  /**
55125
55125
  * @internal
55126
55126
  */
55127
- newRoot(fs12) {
55128
- return new PathWin32(this.rootPath, IFDIR, void 0, this.roots, this.nocase, this.childrenCache(), { fs: fs12 });
55127
+ newRoot(fs13) {
55128
+ return new PathWin32(this.rootPath, IFDIR, void 0, this.roots, this.nocase, this.childrenCache(), { fs: fs13 });
55129
55129
  }
55130
55130
  /**
55131
55131
  * Return true if the provided path string is an absolute path
@@ -55153,8 +55153,8 @@ var PathScurryPosix = class extends PathScurryBase {
55153
55153
  /**
55154
55154
  * @internal
55155
55155
  */
55156
- newRoot(fs12) {
55157
- return new PathPosix(this.rootPath, IFDIR, void 0, this.roots, this.nocase, this.childrenCache(), { fs: fs12 });
55156
+ newRoot(fs13) {
55157
+ return new PathPosix(this.rootPath, IFDIR, void 0, this.roots, this.nocase, this.childrenCache(), { fs: fs13 });
55158
55158
  }
55159
55159
  /**
55160
55160
  * Return true if the provided path string is an absolute path
@@ -56958,7 +56958,7 @@ var SSEManager = class {
56958
56958
  var sseManager = new SSEManager();
56959
56959
 
56960
56960
  // src/proxy/version.ts
56961
- var VERSION = "0.3.54";
56961
+ var VERSION = "0.3.56";
56962
56962
 
56963
56963
  // node_modules/uuid/dist/esm/stringify.js
56964
56964
  var byteToHex = [];
@@ -111260,6 +111260,77 @@ function chooseAgent(agentName) {
111260
111260
  return agent;
111261
111261
  }
111262
111262
 
111263
+ // src/agent/validate.ts
111264
+ var fs10 = __toESM(require("fs/promises"), 1);
111265
+ var SYSTEM2 = `You are a frontend validation expert. Your job is to analyze logs and a screenshot to determine if a frontend application is running correctly.
111266
+
111267
+ You will be provided with:
111268
+ 1. PM2 process logs from all services
111269
+ 2. A screenshot of the frontend application
111270
+
111271
+ Analyze both to determine if the frontend is working properly. Look for:
111272
+ - Error messages in logs
111273
+ - Failed dependencies or missing services
111274
+ - Whether the screenshot shows a properly loaded page or an error page
111275
+ - Any indication the service is not functioning correctly
111276
+
111277
+ Respond with either "<status>OK</status>" if everything looks good, or "<status>ERROR</status><message>Description of what's wrong</message>" if there are issues.`;
111278
+ var makeValidationPrompt = (logs) => {
111279
+ return `Please validate that the frontend service is running correctly.
111280
+
111281
+ Here are the logs from all PM2 services:
111282
+
111283
+ ${logs}
111284
+
111285
+ Please analyze:
111286
+ 1. The logs above to check for errors, warnings, or issues
111287
+ 2. The screenshot I've attached to verify the page loaded correctly
111288
+ 3. Determine if the service is healthy or if there are problems
111289
+
111290
+ Return your analysis in the XML format specified in the system prompt.`;
111291
+ };
111292
+ async function runValidation(screenshotPath, logs, apiKey) {
111293
+ const imageBuffer = await fs10.readFile(screenshotPath);
111294
+ const base64Image = imageBuffer.toString("base64");
111295
+ const systemMessage = {
111296
+ role: "system",
111297
+ content: SYSTEM2
111298
+ };
111299
+ const userMessage = {
111300
+ role: "user",
111301
+ content: [
111302
+ {
111303
+ type: "text",
111304
+ text: makeValidationPrompt(logs)
111305
+ },
111306
+ {
111307
+ type: "image",
111308
+ image: base64Image,
111309
+ mediaType: "image/png"
111310
+ }
111311
+ ]
111312
+ };
111313
+ const messages = [systemMessage, userMessage];
111314
+ const res = await callModel({
111315
+ provider: "anthropic",
111316
+ apiKey,
111317
+ messages,
111318
+ thinkingSpeed: "fast"
111319
+ });
111320
+ return res.text;
111321
+ }
111322
+ var parseValidationResponse = (rawResponse) => {
111323
+ const statusMatch = rawResponse.match(/<status>(.*?)<\/status>/);
111324
+ const messageMatch = rawResponse.match(/<message>(.*?)<\/message>/s);
111325
+ const status = statusMatch ? statusMatch[1] : "UNKNOWN";
111326
+ const message = messageMatch ? messageMatch[1].trim() : "";
111327
+ return {
111328
+ status,
111329
+ message,
111330
+ ok: status === "OK"
111331
+ };
111332
+ };
111333
+
111263
111334
  // src/proxy/server.ts
111264
111335
  var import_promises2 = __toESM(require("fs/promises"), 1);
111265
111336
  var import_path2 = __toESM(require("path"), 1);
@@ -111711,7 +111782,7 @@ Stderr: ${error86.stderr}`
111711
111782
  };
111712
111783
 
111713
111784
  // src/proxy/playwright.ts
111714
- var fs10 = __toESM(require("fs/promises"), 1);
111785
+ var fs11 = __toESM(require("fs/promises"), 1);
111715
111786
  var path9 = __toESM(require("path"), 1);
111716
111787
  var DEFAULT_CONFIG = `import { defineConfig } from "@playwright/test";
111717
111788
  export default defineConfig({
@@ -111911,7 +111982,7 @@ async function findRepositoryLocation(repoName) {
111911
111982
  async function verifyTestFileExists(repoLocation, testFilePath) {
111912
111983
  if (testFilePath.startsWith("/")) {
111913
111984
  try {
111914
- await fs10.access(testFilePath);
111985
+ await fs11.access(testFilePath);
111915
111986
  return testFilePath;
111916
111987
  } catch {
111917
111988
  throw new Error(`Test file not found: ${testFilePath}`);
@@ -111919,7 +111990,7 @@ async function verifyTestFileExists(repoLocation, testFilePath) {
111919
111990
  }
111920
111991
  const testFullPath = path9.join(repoLocation, testFilePath);
111921
111992
  try {
111922
- await fs10.access(testFullPath);
111993
+ await fs11.access(testFullPath);
111923
111994
  return testFullPath;
111924
111995
  } catch {
111925
111996
  throw new Error(`Test file not found: ${testFilePath}`);
@@ -111934,7 +112005,7 @@ async function findPlaywrightConfig(repoLocation) {
111934
112005
  for (const configFile of configFiles) {
111935
112006
  const fullConfigPath = path9.join(repoLocation, configFile);
111936
112007
  try {
111937
- await fs10.access(fullConfigPath);
112008
+ await fs11.access(fullConfigPath);
111938
112009
  return fullConfigPath;
111939
112010
  } catch {
111940
112011
  }
@@ -111943,13 +112014,13 @@ async function findPlaywrightConfig(repoLocation) {
111943
112014
  }
111944
112015
  async function createPlaywrightConfig(repoLocation) {
111945
112016
  const configPath = path9.join(repoLocation, "playwright.config.ts");
111946
- await fs10.writeFile(configPath, DEFAULT_CONFIG);
112017
+ await fs11.writeFile(configPath, DEFAULT_CONFIG);
111947
112018
  log(`Created playwright config at ${configPath}`);
111948
112019
  return configPath;
111949
112020
  }
111950
112021
  async function createTimestampReporter(repoLocation) {
111951
112022
  const reporterPath = path9.join(repoLocation, "timestamp-reporter.ts");
111952
- await fs10.writeFile(reporterPath, TIMESTAMP_REPORTER_CODE);
112023
+ await fs11.writeFile(reporterPath, TIMESTAMP_REPORTER_CODE);
111953
112024
  log(`Created timestamp reporter at ${reporterPath}`);
111954
112025
  return reporterPath;
111955
112026
  }
@@ -111964,7 +112035,7 @@ async function setupPlaywrightConfig(repoLocation) {
111964
112035
  wasCreated = true;
111965
112036
  log("Created new playwright config");
111966
112037
  } else {
111967
- originalContent = await fs10.readFile(configPath, "utf-8");
112038
+ originalContent = await fs11.readFile(configPath, "utf-8");
111968
112039
  wasModified = true;
111969
112040
  const newConfig = `import { defineConfig } from "@playwright/test";
111970
112041
 
@@ -111984,7 +112055,7 @@ export default defineConfig({
111984
112055
  },
111985
112056
  });
111986
112057
  `;
111987
- await fs10.writeFile(configPath, newConfig);
112058
+ await fs11.writeFile(configPath, newConfig);
111988
112059
  log(
111989
112060
  `Overwrote existing config at ${configPath} (backed up for cleanup)`
111990
112061
  );
@@ -112002,7 +112073,7 @@ async function ensureTestResultsInGitignore(repoLocation) {
112002
112073
  const gitignorePath = path9.join(repoLocation, ".gitignore");
112003
112074
  const testResultsEntry = "test-results";
112004
112075
  try {
112005
- const gitignoreContent = await fs10.readFile(gitignorePath, "utf-8");
112076
+ const gitignoreContent = await fs11.readFile(gitignorePath, "utf-8");
112006
112077
  const lines = gitignoreContent.split("\n");
112007
112078
  const hasTestResults = lines.some(
112008
112079
  (line) => line.trim() === testResultsEntry
@@ -112015,10 +112086,10 @@ async function ensureTestResultsInGitignore(repoLocation) {
112015
112086
  ` : `${gitignoreContent}
112016
112087
  ${testResultsEntry}
112017
112088
  `;
112018
- await fs10.writeFile(gitignorePath, updatedContent);
112089
+ await fs11.writeFile(gitignorePath, updatedContent);
112019
112090
  log("Added test-results to .gitignore");
112020
112091
  } catch (error86) {
112021
- await fs10.writeFile(gitignorePath, `${testResultsEntry}
112092
+ await fs11.writeFile(gitignorePath, `${testResultsEntry}
112022
112093
  `);
112023
112094
  log("Created .gitignore with test-results");
112024
112095
  }
@@ -112043,10 +112114,10 @@ async function findVideoFile(repoLocation) {
112043
112114
  const testResultsDir = path9.join(repoLocation, "test-results");
112044
112115
  const walkDir = async (dir) => {
112045
112116
  try {
112046
- const files = await fs10.readdir(dir);
112117
+ const files = await fs11.readdir(dir);
112047
112118
  for (const file3 of files) {
112048
112119
  const fullPath = path9.join(dir, file3);
112049
- const stat4 = await fs10.stat(fullPath);
112120
+ const stat4 = await fs11.stat(fullPath);
112050
112121
  if (stat4.isDirectory()) {
112051
112122
  const result = await walkDir(fullPath);
112052
112123
  if (result) {
@@ -112072,10 +112143,10 @@ async function findTimestampJsonFile(repoLocation) {
112072
112143
  const testResultsDir = path9.join(repoLocation, "test-results");
112073
112144
  const walkDir = async (dir) => {
112074
112145
  try {
112075
- const files = await fs10.readdir(dir);
112146
+ const files = await fs11.readdir(dir);
112076
112147
  for (const file3 of files) {
112077
112148
  const fullPath = path9.join(dir, file3);
112078
- const stat4 = await fs10.stat(fullPath);
112149
+ const stat4 = await fs11.stat(fullPath);
112079
112150
  if (stat4.isDirectory()) {
112080
112151
  const result = await walkDir(fullPath);
112081
112152
  if (result) {
@@ -112098,9 +112169,9 @@ async function findTimestampJsonFile(repoLocation) {
112098
112169
  return jsonPath;
112099
112170
  }
112100
112171
  async function uploadVideo(videoPath, timestampJsonPath, responseUrl, apiKey) {
112101
- const videoBuffer = await fs10.readFile(videoPath);
112172
+ const videoBuffer = await fs11.readFile(videoPath);
112102
112173
  log(`Read video file: ${videoBuffer.length} bytes`);
112103
- const jsonBuffer = await fs10.readFile(timestampJsonPath);
112174
+ const jsonBuffer = await fs11.readFile(timestampJsonPath);
112104
112175
  log(`Read timestamp JSON file: ${jsonBuffer.length} bytes`);
112105
112176
  const { FormData: FormData2, fetch: undiciFetch } = await Promise.resolve().then(() => __toESM(require_undici(), 1));
112106
112177
  const formData = new FormData2();
@@ -112135,13 +112206,13 @@ async function uploadVideo(videoPath, timestampJsonPath, responseUrl, apiKey) {
112135
112206
  }
112136
112207
  async function deleteTestFiles(videoPath, timestampJsonPath) {
112137
112208
  try {
112138
- await fs10.unlink(videoPath);
112209
+ await fs11.unlink(videoPath);
112139
112210
  log(`Deleted video file: ${videoPath}`);
112140
112211
  } catch (error86) {
112141
112212
  error(`Error deleting video file ${videoPath}:`, error86);
112142
112213
  }
112143
112214
  try {
112144
- await fs10.unlink(timestampJsonPath);
112215
+ await fs11.unlink(timestampJsonPath);
112145
112216
  log(`Deleted timestamp JSON: ${timestampJsonPath}`);
112146
112217
  } catch (error86) {
112147
112218
  error(`Error deleting timestamp JSON ${timestampJsonPath}:`, error86);
@@ -112151,12 +112222,12 @@ async function cleanupConfig(configState) {
112151
112222
  if (configState.configPath) {
112152
112223
  try {
112153
112224
  if (configState.wasCreated) {
112154
- await fs10.unlink(configState.configPath);
112225
+ await fs11.unlink(configState.configPath);
112155
112226
  log(
112156
112227
  `Cleaned up created config file: ${configState.configPath}`
112157
112228
  );
112158
112229
  } else if (configState.wasModified && configState.originalContent) {
112159
- await fs10.writeFile(configState.configPath, configState.originalContent);
112230
+ await fs11.writeFile(configState.configPath, configState.originalContent);
112160
112231
  log(`Restored original config: ${configState.configPath}`);
112161
112232
  }
112162
112233
  } catch (error86) {
@@ -112165,7 +112236,7 @@ async function cleanupConfig(configState) {
112165
112236
  }
112166
112237
  if (configState.reporterPath && configState.reporterWasCreated) {
112167
112238
  try {
112168
- await fs10.unlink(configState.reporterPath);
112239
+ await fs11.unlink(configState.reporterPath);
112169
112240
  log(`Cleaned up reporter file: ${configState.reporterPath}`);
112170
112241
  } catch (error86) {
112171
112242
  error("Error cleaning up reporter:", error86);
@@ -112221,6 +112292,35 @@ async function runPlaywrightTestWithVideo(options) {
112221
112292
  }
112222
112293
  }
112223
112294
 
112295
+ // src/proxy/screenshot.ts
112296
+ var import_playwright_core = require("playwright-core");
112297
+ async function takeScreenshot(port) {
112298
+ const browser = await import_playwright_core.chromium.launch({
112299
+ headless: true,
112300
+ args: ["--no-sandbox", "--disable-setuid-sandbox"]
112301
+ });
112302
+ try {
112303
+ const context = await browser.newContext();
112304
+ const page = await context.newPage();
112305
+ const url3 = `http://localhost:${port}`;
112306
+ log(`Navigating to ${url3} for screenshot`);
112307
+ await page.goto(url3, {
112308
+ waitUntil: "networkidle",
112309
+ timeout: 3e4
112310
+ });
112311
+ const timestamp = Date.now();
112312
+ const screenshotPath = `/tmp/staklink-validation-${timestamp}.png`;
112313
+ await page.screenshot({
112314
+ path: screenshotPath,
112315
+ fullPage: true
112316
+ });
112317
+ log(`Screenshot saved to ${screenshotPath}`);
112318
+ return screenshotPath;
112319
+ } finally {
112320
+ await browser.close();
112321
+ }
112322
+ }
112323
+
112224
112324
  // src/proxy/server.ts
112225
112325
  var PORT = parseInt(process.env.STAKLINK_PORT || "15552") || 15552;
112226
112326
  var VSCODE_EXTENSION_URL = `http://localhost:${PORT + 1}`;
@@ -113193,6 +113293,58 @@ ${diff.trim()}`);
113193
113293
  }
113194
113294
  }
113195
113295
  );
113296
+ app.post(
113297
+ "/validate_frontend",
113298
+ async (req, res) => {
113299
+ log("===> POST /validate_frontend");
113300
+ try {
113301
+ const workspaceRoot2 = await workspaceRoot();
113302
+ const config3 = await findAndLoadPm2Config(workspaceRoot2, log);
113303
+ const apiKey = req.body.apiKey;
113304
+ if (!apiKey) {
113305
+ throw new Error("apiKey is required");
113306
+ }
113307
+ const runner = new Runner(workspaceRoot2, log);
113308
+ const logsMap = {};
113309
+ for (const app2 of config3.apps) {
113310
+ try {
113311
+ const logs = await runner.getProcessLogs(app2.name, 100);
113312
+ logsMap[app2.name] = logs;
113313
+ } catch (error86) {
113314
+ error(`Failed to get logs for ${app2.name}:`, error86);
113315
+ logsMap[app2.name] = `ERROR: ${error86}`;
113316
+ }
113317
+ }
113318
+ let logsContent = "";
113319
+ for (const [appName, logs] of Object.entries(logsMap)) {
113320
+ logsContent += `
113321
+ ========== ${appName} ==========
113322
+ ${logs}
113323
+ `;
113324
+ }
113325
+ const frontendApp = config3.apps.find(
113326
+ (app2) => app2.env?.PORT || app2.name.toLowerCase().includes("frontend")
113327
+ );
113328
+ const port = frontendApp?.env?.PORT || "3000";
113329
+ const screenshotPath = await takeScreenshot(Number(port));
113330
+ log(`Screenshot saved to: ${screenshotPath}`);
113331
+ const result = await runValidation(
113332
+ screenshotPath,
113333
+ logsContent,
113334
+ apiKey
113335
+ );
113336
+ const validation = parseValidationResponse(result);
113337
+ try {
113338
+ } catch (error86) {
113339
+ warn("Failed to cleanup screenshot:", error86);
113340
+ }
113341
+ res.json({ success: true, ...validation });
113342
+ } catch (error86) {
113343
+ error("Error in validate_frontend:", error86);
113344
+ fail(res, error86);
113345
+ }
113346
+ }
113347
+ );
113196
113348
  app.get("/leaks", async (req, res) => {
113197
113349
  log(`===> GET /leaks`);
113198
113350
  try {
@@ -10967,7 +10967,7 @@ var glob = Object.assign(glob_, {
10967
10967
  glob.glob = glob;
10968
10968
 
10969
10969
  // src/proxy/version.ts
10970
- var VERSION = "0.3.54";
10970
+ var VERSION = "0.3.56";
10971
10971
 
10972
10972
  // src/cli.ts
10973
10973
  var STAKLINK_PROXY = "staklink-proxy";
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "staklink",
3
3
  "displayName": "staklink",
4
4
  "description": "staklink process manager",
5
- "version": "0.3.54",
5
+ "version": "0.3.56",
6
6
  "type": "module",
7
7
  "publisher": "stakwork",
8
8
  "engines": {
@@ -124,6 +124,7 @@
124
124
  "express": "^4.21.2",
125
125
  "glob": "^11.0.2",
126
126
  "jsonc-parser": "^3.3.1",
127
+ "playwright-core": "^1.56.1",
127
128
  "undici": "^7.16.0",
128
129
  "uuid": "^11.1.0",
129
130
  "vscode-uri": "^3.1.0",