lavish-axi 0.1.83 → 0.1.84

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 (3) hide show
  1. package/dist/cli.mjs +134 -75
  2. package/package.json +1 -1
  3. package/plugin.json +1 -1
package/dist/cli.mjs CHANGED
@@ -21,7 +21,7 @@ import { access, readFile as readFile6, writeFile as writeFile4 } from "node:fs/
21
21
  import { get as httpGet2 } from "node:http";
22
22
  import { isIP as isIP4 } from "node:net";
23
23
  import os4 from "node:os";
24
- import path8 from "node:path";
24
+ import path9 from "node:path";
25
25
  import { fileURLToPath as fileURLToPath4 } from "node:url";
26
26
  import { AxiError, installSessionStartHooks, RESERVED_COMMANDS, runAxiCli } from "axi-sdk-js";
27
27
 
@@ -4427,7 +4427,7 @@ function htmlAppRequestError(message, details = {}) {
4427
4427
  if (status !== void 0) Object.defineProperty(error, "status", { value: status, enumerable: true });
4428
4428
  return error;
4429
4429
  }
4430
- async function requestHtmlApp({ method, path: path9, body, bearer, options, env, action = "publish" }) {
4430
+ async function requestHtmlApp({ method, path: path10, body, bearer, options, env, action = "publish" }) {
4431
4431
  const apiUrl = (options.apiUrl ? String(options.apiUrl).replace(/\/+$/, "") : "") || htmlAppApiUrl(env);
4432
4432
  const fetchImpl = options.fetch || fetch;
4433
4433
  const headers = { "content-type": "application/json", "user-agent": "lavish-axi" };
@@ -4437,7 +4437,7 @@ async function requestHtmlApp({ method, path: path9, body, bearer, options, env,
4437
4437
  let response;
4438
4438
  let text;
4439
4439
  try {
4440
- response = await fetchImpl(`${apiUrl}${path9}`, {
4440
+ response = await fetchImpl(`${apiUrl}${path10}`, {
4441
4441
  method,
4442
4442
  headers,
4443
4443
  body: JSON.stringify(body),
@@ -4704,10 +4704,10 @@ function writeTextFileAtomically(file, content, operations = {}) {
4704
4704
  const rename3 = operations.renameSync || renameSync;
4705
4705
  const remove = operations.rmSync || rmSync;
4706
4706
  const chmod2 = operations.chmodSync || chmodSync;
4707
- const stat3 = operations.statSync || statSync;
4707
+ const stat4 = operations.statSync || statSync;
4708
4708
  let mode;
4709
4709
  try {
4710
- mode = stat3(file).mode & 511;
4710
+ mode = stat4(file).mode & 511;
4711
4711
  } catch {
4712
4712
  }
4713
4713
  try {
@@ -4829,11 +4829,11 @@ function attrValue(attrs, name) {
4829
4829
  import crypto6 from "node:crypto";
4830
4830
  import { EventEmitter } from "node:events";
4831
4831
  import { existsSync as existsSync2 } from "node:fs";
4832
- import { readFile as readFile5, realpath as realpath3 } from "node:fs/promises";
4832
+ import { readFile as readFile5, realpath as realpath4 } from "node:fs/promises";
4833
4833
  import { createServer, get as httpGet } from "node:http";
4834
4834
  import { isIP as isIP3 } from "node:net";
4835
4835
  import { homedir } from "node:os";
4836
- import path7 from "node:path";
4836
+ import path8 from "node:path";
4837
4837
  import { fileURLToPath as fileURLToPath3 } from "node:url";
4838
4838
  import chokidar from "chokidar";
4839
4839
  import express from "express";
@@ -5925,15 +5925,15 @@ function createArtifactSdk(deriveQueueKey, isNativeInteractive = isNativeInterac
5925
5925
  return node.parentElement || document.body;
5926
5926
  }
5927
5927
  function nodePath(node, root) {
5928
- const path9 = [];
5928
+ const path10 = [];
5929
5929
  let current = node;
5930
5930
  while (current && current !== root) {
5931
5931
  const parentNode = current.parentNode;
5932
5932
  if (!parentNode) break;
5933
- path9.unshift([...parentNode.childNodes].indexOf(current));
5933
+ path10.unshift([...parentNode.childNodes].indexOf(current));
5934
5934
  current = parentNode;
5935
5935
  }
5936
- return path9;
5936
+ return path10;
5937
5937
  }
5938
5938
  function rangeBoundary(node, offset) {
5939
5939
  const el = closestElement(node);
@@ -8459,10 +8459,53 @@ function isIncompleteRunningTailscaleStatus(raw) {
8459
8459
  return addresses.some((address) => typeof address === "string" && isIP2(address.trim()) === 4);
8460
8460
  }
8461
8461
 
8462
+ // src/artifact-path.js
8463
+ import { realpath as realpath2, stat as stat2 } from "node:fs/promises";
8464
+ import path5 from "node:path";
8465
+ var ARTIFACT_HTML_ERROR = "Lavish Editor expects an HTML file";
8466
+ function isHtmlPath(file) {
8467
+ const lower = String(file || "").toLowerCase();
8468
+ return lower.endsWith(".html") || lower.endsWith(".htm");
8469
+ }
8470
+ function artifactTreeRoot(file) {
8471
+ return path5.dirname(file);
8472
+ }
8473
+ var ArtifactPathError = class extends Error {
8474
+ /** @type {"VALIDATION_ERROR"} */
8475
+ code = "VALIDATION_ERROR";
8476
+ /** @type {400} */
8477
+ statusCode = 400;
8478
+ /**
8479
+ * @param {string} [message]
8480
+ */
8481
+ constructor(message = ARTIFACT_HTML_ERROR) {
8482
+ super(message);
8483
+ this.name = "ArtifactPathError";
8484
+ }
8485
+ };
8486
+ function isArtifactPathError(error) {
8487
+ return error instanceof Error && error.name === "ArtifactPathError";
8488
+ }
8489
+ async function resolveAllowedArtifactFile(file) {
8490
+ const requested = typeof file === "string" ? file : "";
8491
+ if (!isHtmlPath(requested)) {
8492
+ throw new ArtifactPathError();
8493
+ }
8494
+ const canonical = await realpath2(path5.resolve(requested));
8495
+ if (!isHtmlPath(canonical)) {
8496
+ throw new ArtifactPathError();
8497
+ }
8498
+ const info = await stat2(canonical);
8499
+ if (!info.isFile()) {
8500
+ throw new ArtifactPathError();
8501
+ }
8502
+ return canonical;
8503
+ }
8504
+
8462
8505
  // src/session-store.js
8463
8506
  import crypto4 from "node:crypto";
8464
- import { readFile as readFile3, realpath as realpath2, writeFile as writeFile2 } from "node:fs/promises";
8465
- import path5 from "node:path";
8507
+ import { readFile as readFile3, realpath as realpath3, writeFile as writeFile2 } from "node:fs/promises";
8508
+ import path6 from "node:path";
8466
8509
 
8467
8510
  // src/async-mutex.js
8468
8511
  var AsyncMutex = class {
@@ -9118,8 +9161,8 @@ var SessionStore = class {
9118
9161
  }
9119
9162
  };
9120
9163
  async function canonicalFile(file) {
9121
- const absolute = path5.resolve(file);
9122
- return realpath2(absolute);
9164
+ const absolute = path6.resolve(file);
9165
+ return realpath3(absolute);
9123
9166
  }
9124
9167
  function sessionKey(file) {
9125
9168
  return crypto4.createHash("sha256").update(file).digest("hex").slice(0, 16);
@@ -9359,8 +9402,8 @@ function generateSharePassword() {
9359
9402
 
9360
9403
  // src/attachment-store.js
9361
9404
  import crypto5 from "node:crypto";
9362
- import { chmod, mkdir as mkdir3, readdir, readFile as readFile4, rename as rename2, rm as rm2, stat as stat2, utimes, writeFile as writeFile3 } from "node:fs/promises";
9363
- import path6 from "node:path";
9405
+ import { chmod, mkdir as mkdir3, readdir, readFile as readFile4, rename as rename2, rm as rm2, stat as stat3, utimes, writeFile as writeFile3 } from "node:fs/promises";
9406
+ import path7 from "node:path";
9364
9407
  var KEY_RE2 = /^[0-9a-f]{16}$/;
9365
9408
  var ID_RE = /^[0-9a-f]{64}\.(png|jpg|webp)$/;
9366
9409
  var MIME_BY_EXT = { png: "image/png", jpg: "image/jpeg", webp: "image/webp" };
@@ -9388,10 +9431,10 @@ function isValidAttachmentId(id) {
9388
9431
  return ID_RE.test(String(id || ""));
9389
9432
  }
9390
9433
  function attachmentsDir(stateDir2, key) {
9391
- return path6.join(stateDir2, "attachments", String(key));
9434
+ return path7.join(stateDir2, "attachments", String(key));
9392
9435
  }
9393
9436
  function attachmentFile(stateDir2, key, id) {
9394
- return path6.join(attachmentsDir(stateDir2, key), id);
9437
+ return path7.join(attachmentsDir(stateDir2, key), id);
9395
9438
  }
9396
9439
  function resolveAttachmentConfig(env = process.env) {
9397
9440
  const maxDiskBytes = diskCapEnv(env.LAVISH_AXI_MAX_ATTACHMENT_DISK_MB);
@@ -9526,7 +9569,7 @@ function buildMetadata(id, file, mime, bytes, dims) {
9526
9569
  }
9527
9570
  async function pathExists(file) {
9528
9571
  try {
9529
- await stat2(file);
9572
+ await stat3(file);
9530
9573
  return true;
9531
9574
  } catch (error) {
9532
9575
  if (error && error.code === "ENOENT") return false;
@@ -9566,7 +9609,7 @@ async function writeFileAtomically2(file, content) {
9566
9609
  }
9567
9610
  }
9568
9611
  async function ensureAttachmentDir(stateDir2, key) {
9569
- const root = path6.join(stateDir2, "attachments");
9612
+ const root = path7.join(stateDir2, "attachments");
9570
9613
  const dir = attachmentsDir(stateDir2, key);
9571
9614
  await mkdir3(dir, { recursive: true, mode: ATTACHMENT_DIR_MODE });
9572
9615
  for (const target of [root, dir]) {
@@ -9647,7 +9690,7 @@ async function resolveAttachment(stateDir2, key, id) {
9647
9690
  const file = attachmentFile(stateDir2, key, id);
9648
9691
  let info;
9649
9692
  try {
9650
- info = await stat2(file);
9693
+ info = await stat3(file);
9651
9694
  } catch (error) {
9652
9695
  if (error && error.code === "ENOENT") return null;
9653
9696
  throw error;
@@ -9661,7 +9704,7 @@ async function statAttachmentForServe(stateDir2, key, id) {
9661
9704
  if (!isValidAttachmentKey(key) || !isValidAttachmentId(id)) return null;
9662
9705
  const file = attachmentFile(stateDir2, key, id);
9663
9706
  try {
9664
- const info = await stat2(file);
9707
+ const info = await stat3(file);
9665
9708
  if (!info.isFile()) return null;
9666
9709
  } catch (error) {
9667
9710
  if (error && error.code === "ENOENT") return null;
@@ -9690,20 +9733,20 @@ async function removeAttachment(stateDir2, key, id) {
9690
9733
  }
9691
9734
  }
9692
9735
  async function listAttachments(stateDir2) {
9693
- const root = path6.join(stateDir2, "attachments");
9736
+ const root = path7.join(stateDir2, "attachments");
9694
9737
  const sessionDirs = await readdirSafe(root);
9695
9738
  const files = [];
9696
9739
  for (const dirent of sessionDirs) {
9697
9740
  if (!dirent.isDirectory() || !isValidAttachmentKey(dirent.name)) continue;
9698
- const dir = path6.join(root, dirent.name);
9741
+ const dir = path7.join(root, dirent.name);
9699
9742
  for (const entry of await readdirSafe(dir)) {
9700
9743
  if (!entry.isFile() || !isValidAttachmentId(entry.name)) continue;
9701
- const filePath = path6.join(dir, entry.name);
9744
+ const filePath = path7.join(dir, entry.name);
9702
9745
  try {
9703
- const info = await stat2(filePath);
9746
+ const info = await stat3(filePath);
9704
9747
  let sidecarBytes = 0;
9705
9748
  try {
9706
- sidecarBytes = (await stat2(sidecarPath(filePath))).size;
9749
+ sidecarBytes = (await stat3(sidecarPath(filePath))).size;
9707
9750
  } catch {
9708
9751
  }
9709
9752
  files.push({
@@ -9780,19 +9823,19 @@ async function sweepAttachments(stateDir2, options = {}) {
9780
9823
  const orphans = await reapOrphanFiles(stateDir2, now);
9781
9824
  deleted += orphans.deleted;
9782
9825
  freedBytes += orphans.freedBytes;
9783
- await pruneEmptyDirs(path6.join(stateDir2, "attachments"));
9826
+ await pruneEmptyDirs(path7.join(stateDir2, "attachments"));
9784
9827
  return { deleted, freedBytes };
9785
9828
  }
9786
9829
  async function committedChargedBytes(stateDir2) {
9787
- const root = path6.join(stateDir2, "attachments");
9830
+ const root = path7.join(stateDir2, "attachments");
9788
9831
  let total = 0;
9789
9832
  for (const dirent of await readdirSafe(root)) {
9790
9833
  if (!dirent.isDirectory() || !isValidAttachmentKey(dirent.name)) continue;
9791
- const dir = path6.join(root, dirent.name);
9834
+ const dir = path7.join(root, dirent.name);
9792
9835
  for (const entry of await readdirSafe(dir)) {
9793
9836
  if (!entry.isFile()) continue;
9794
9837
  try {
9795
- total += allocatedBytes((await stat2(path6.join(dir, entry.name))).size);
9838
+ total += allocatedBytes((await stat3(path7.join(dir, entry.name))).size);
9796
9839
  } catch {
9797
9840
  }
9798
9841
  }
@@ -9800,28 +9843,28 @@ async function committedChargedBytes(stateDir2) {
9800
9843
  return total;
9801
9844
  }
9802
9845
  async function reapOrphanFiles(stateDir2, now) {
9803
- const root = path6.join(stateDir2, "attachments");
9846
+ const root = path7.join(stateDir2, "attachments");
9804
9847
  let deleted = 0;
9805
9848
  let freedBytes = 0;
9806
9849
  for (const dirent of await readdirSafe(root)) {
9807
9850
  if (!dirent.isDirectory() || !isValidAttachmentKey(dirent.name)) continue;
9808
- const dir = path6.join(root, dirent.name);
9851
+ const dir = path7.join(root, dirent.name);
9809
9852
  const entries = await readdirSafe(dir);
9810
9853
  const present = new Set(entries.filter((e) => e.isFile()).map((e) => e.name));
9811
9854
  for (const entry of entries) {
9812
9855
  if (!entry.isFile()) continue;
9813
- const filePath = path6.join(dir, entry.name);
9856
+ const filePath = path7.join(dir, entry.name);
9814
9857
  const sidecarMatch = SIDECAR_ORPHAN_RE.exec(entry.name);
9815
9858
  try {
9816
9859
  if (TEMP_FILE_RE.test(entry.name)) {
9817
- const info = await stat2(filePath);
9860
+ const info = await stat3(filePath);
9818
9861
  if (now - info.mtimeMs <= ATTACHMENT_TEMP_GRACE_MS) continue;
9819
9862
  if (await removeFile(filePath)) {
9820
9863
  deleted += 1;
9821
9864
  freedBytes += info.size;
9822
9865
  }
9823
9866
  } else if (sidecarMatch && !present.has(sidecarMatch[1])) {
9824
- const info = await stat2(filePath);
9867
+ const info = await stat3(filePath);
9825
9868
  if (await removeFile(filePath)) {
9826
9869
  deleted += 1;
9827
9870
  freedBytes += info.size;
@@ -9854,7 +9897,7 @@ async function removeFile(file) {
9854
9897
  async function pruneEmptyDirs(root) {
9855
9898
  for (const dirent of await readdirSafe(root)) {
9856
9899
  if (!dirent.isDirectory()) continue;
9857
- const dir = path6.join(root, dirent.name);
9900
+ const dir = path7.join(root, dirent.name);
9858
9901
  try {
9859
9902
  if ((await readdir(dir)).length === 0) await rm2(dir, { recursive: true, force: true });
9860
9903
  } catch {
@@ -10233,7 +10276,7 @@ async function serve({
10233
10276
  if (pendingAfterRestore) events.emit("feedback", key);
10234
10277
  await publishChatSync(key);
10235
10278
  }
10236
- const whiteboardStateRoot = path7.dirname(stateFile2);
10279
+ const whiteboardStateRoot = path8.dirname(stateFile2);
10237
10280
  const allowedHostnames = buildAllowedHostnames({
10238
10281
  host: requestedListenHosts[0],
10239
10282
  hosts: [...requestedListenHosts, ...listenHosts],
@@ -10265,6 +10308,11 @@ async function serve({
10265
10308
  workingUrl: workingUrlFor(req, { includeSessionPath: false })
10266
10309
  });
10267
10310
  }
10311
+ function refuseNonHtmlArtifact(session, res) {
10312
+ if (isHtmlPath(session.file)) return false;
10313
+ res.status(403).send("Forbidden");
10314
+ return true;
10315
+ }
10268
10316
  if (!allowAnyHostname) {
10269
10317
  app.use((req, res, next) => {
10270
10318
  const requestHost = { host: req.headers.host, forwardedHost: req.headers["x-forwarded-host"] };
@@ -10295,7 +10343,7 @@ async function serve({
10295
10343
  next();
10296
10344
  });
10297
10345
  const attachmentConfig = resolveAttachmentConfig();
10298
- const attachmentStateRoot = path7.dirname(stateFile2);
10346
+ const attachmentStateRoot = path8.dirname(stateFile2);
10299
10347
  const defaultJsonParser = express.json({ limit: "2mb" });
10300
10348
  const whiteboardJsonParser = express.json({ limit: "20mb" });
10301
10349
  app.use((req, res, next) => {
@@ -10318,7 +10366,7 @@ async function serve({
10318
10366
  app: "lavish-axi",
10319
10367
  version,
10320
10368
  state_id: serverStateId,
10321
- state_dir: path7.dirname(stateFile2),
10369
+ state_dir: path8.dirname(stateFile2),
10322
10370
  // Where this process is listening and every address it was asked to serve (bound or still
10323
10371
  // retrying). The CLI uses both to find one daemon per port whatever host it was configured
10324
10372
  // with, and to tell a same-port daemon at another address apart from this one.
@@ -10346,7 +10394,7 @@ async function serve({
10346
10394
  });
10347
10395
  app.post("/api/sessions", async (req, res, next) => {
10348
10396
  try {
10349
- const file = await canonicalFile(req.body.file);
10397
+ const file = await resolveAllowedArtifactFile(req.body?.file);
10350
10398
  const key = sessionKey(file);
10351
10399
  const reopen = Boolean(req.body.reopen);
10352
10400
  const existing = await store.findByKey(key);
@@ -10378,6 +10426,14 @@ async function serve({
10378
10426
  ...networkWarningField()
10379
10427
  });
10380
10428
  } catch (error) {
10429
+ if (isArtifactPathError(error)) {
10430
+ res.status(error.statusCode).json({
10431
+ status: "error",
10432
+ code: error.code,
10433
+ error: error.message
10434
+ });
10435
+ return;
10436
+ }
10381
10437
  next(error);
10382
10438
  }
10383
10439
  });
@@ -10806,8 +10862,9 @@ async function serve({
10806
10862
  res.status(404).json({ error: "session not found" });
10807
10863
  return;
10808
10864
  }
10865
+ if (refuseNonHtmlArtifact(session, res)) return;
10809
10866
  const source = await readFile5(session.file, "utf8");
10810
- const root = path7.dirname(session.file);
10867
+ const root = path8.dirname(session.file);
10811
10868
  const { html, warnings } = await buildSelfContainedHtml(source, {
10812
10869
  baseDir: root,
10813
10870
  confineDir: root,
@@ -10834,11 +10891,12 @@ async function serve({
10834
10891
  res.status(404).json({ error: "session not found" });
10835
10892
  return;
10836
10893
  }
10894
+ if (refuseNonHtmlArtifact(session, res)) return;
10837
10895
  const body = req.body || {};
10838
10896
  const generatePassword = body.generate_password === true;
10839
10897
  const password = generatePassword ? generateSharePassword() : optionalBodyString(body.password);
10840
10898
  const source = await readFile5(session.file, "utf8");
10841
- const root = path7.dirname(session.file);
10899
+ const root = path8.dirname(session.file);
10842
10900
  const { html, warnings } = await buildSelfContainedHtml(source, {
10843
10901
  baseDir: root,
10844
10902
  confineDir: root,
@@ -10909,6 +10967,7 @@ async function serve({
10909
10967
  return;
10910
10968
  }
10911
10969
  const session = chromeLoad.session;
10970
+ if (refuseNonHtmlArtifact(session, res)) return;
10912
10971
  await watchSession(session, watchers, events, logEvent, reloadDebounceMs);
10913
10972
  const artifactHtml = await readFile5(session.file, "utf8").catch(() => "");
10914
10973
  const { faviconTag, title } = extractArtifactHead(artifactHtml);
@@ -10986,6 +11045,7 @@ async function serve({
10986
11045
  sendSessionNotFound(req, res);
10987
11046
  return;
10988
11047
  }
11048
+ if (refuseNonHtmlArtifact(beforeRead.session, res)) return;
10989
11049
  if (!beforeRead.valid) {
10990
11050
  res.status(409).type("html").send(
10991
11051
  "<!doctype html><title>Artifact load expired</title><p>This artifact load is no longer current. Reload Lavish to continue.</p>"
@@ -11015,7 +11075,8 @@ async function serve({
11015
11075
  sendSessionNotFound(req, res);
11016
11076
  return;
11017
11077
  }
11018
- const root = path7.dirname(session.file);
11078
+ if (refuseNonHtmlArtifact(session, res)) return;
11079
+ const root = artifactTreeRoot(session.file);
11019
11080
  const file = await resolveArtifactAsset(root, assetPath);
11020
11081
  if (!file) {
11021
11082
  res.status(403).send("Forbidden");
@@ -11122,6 +11183,7 @@ data: ${JSON.stringify({ reason: "server-restarted" })}
11122
11183
  res.status(404).json({ error: "session not found" });
11123
11184
  return;
11124
11185
  }
11186
+ if (refuseNonHtmlArtifact(session, res)) return;
11125
11187
  const html = await readFile5(session.file, "utf8").catch(() => "");
11126
11188
  const sources = extractMermaidSources(html).map(({ index, source }) => ({
11127
11189
  index,
@@ -11837,14 +11899,14 @@ function optionalBodyString(value) {
11837
11899
  return trimmed || void 0;
11838
11900
  }
11839
11901
  async function resolveArtifactAsset(root, assetPath) {
11840
- const file = path7.resolve(root, assetPath);
11841
- const relative = path7.relative(root, file);
11842
- if (relative.startsWith("..") || path7.isAbsolute(relative)) {
11902
+ const file = path8.resolve(root, assetPath);
11903
+ const relative = path8.relative(root, file);
11904
+ if (relative.startsWith("..") || path8.isAbsolute(relative)) {
11843
11905
  return null;
11844
11906
  }
11845
11907
  let real;
11846
11908
  try {
11847
- real = await realpath3(file);
11909
+ real = await realpath4(file);
11848
11910
  } catch (error) {
11849
11911
  if (error?.code === "ENOENT" || error?.code === "ENOTDIR") {
11850
11912
  return file;
@@ -11853,12 +11915,12 @@ async function resolveArtifactAsset(root, assetPath) {
11853
11915
  }
11854
11916
  let realRoot;
11855
11917
  try {
11856
- realRoot = await realpath3(root);
11918
+ realRoot = await realpath4(root);
11857
11919
  } catch {
11858
- realRoot = path7.resolve(root);
11920
+ realRoot = path8.resolve(root);
11859
11921
  }
11860
- const relativeReal = path7.relative(realRoot, real);
11861
- if (relativeReal === ".." || relativeReal.startsWith(`..${path7.sep}`) || path7.isAbsolute(relativeReal)) {
11922
+ const relativeReal = path8.relative(realRoot, real);
11923
+ if (relativeReal === ".." || relativeReal.startsWith(`..${path8.sep}`) || path8.isAbsolute(relativeReal)) {
11862
11924
  return null;
11863
11925
  }
11864
11926
  return real;
@@ -11894,7 +11956,7 @@ async function resolveWatchTarget(session) {
11894
11956
  const html = await readFile5(session.file, "utf8");
11895
11957
  if (hasLiveReloadRootOptIn(html)) {
11896
11958
  return {
11897
- path: path7.dirname(session.file),
11959
+ path: path8.dirname(session.file),
11898
11960
  scope: "directory",
11899
11961
  options: {
11900
11962
  ...baseOptions,
@@ -12348,9 +12410,9 @@ var HttpTelemetryClient = class {
12348
12410
  if (!trimmed) return;
12349
12411
  this.send(trimmed, eventURL(this.app, trimmed), fields);
12350
12412
  }
12351
- pageview(path9, fields = {}) {
12413
+ pageview(path10, fields = {}) {
12352
12414
  if (this.closed) return;
12353
- this.send("", normalizePagePath(path9), fields);
12415
+ this.send("", normalizePagePath(path10), fields);
12354
12416
  }
12355
12417
  async close(timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS) {
12356
12418
  this.closed = true;
@@ -12425,8 +12487,8 @@ function eventURL(app, name) {
12425
12487
  if (!name) return `app://${app}`;
12426
12488
  return `app://${app}/${name.replace(/\./g, "/")}`;
12427
12489
  }
12428
- function normalizePagePath(path9) {
12429
- const trimmed = String(path9 || "").trim();
12490
+ function normalizePagePath(path10) {
12491
+ const trimmed = String(path10 || "").trim();
12430
12492
  if (!trimmed) return "/";
12431
12493
  return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
12432
12494
  }
@@ -12468,7 +12530,7 @@ var AGENT_REPLY_INPUT_LIMIT_BYTES = AGENT_REPLY_JSON_LIMIT_BYTES - AGENT_REPLY_J
12468
12530
  var AGENT_REPLY_LIMIT_LABEL = "2 MB JSON request limit";
12469
12531
  var POLL_STATE_HEADER = "lavish-poll-state";
12470
12532
  var CODEX_POLL_WAKE_PATH_GUIDANCE = "Codex detected: completed background tasks may not resume Codex automatically, so keep the poll attached to the active turn.";
12471
- var VERSION = "0.1.83";
12533
+ var VERSION = "0.1.84";
12472
12534
  function detectInvokingAgent(env = process.env) {
12473
12535
  return ["CODEX_SANDBOX", "CODEX_THREAD_ID"].some((key) => Object.hasOwn(env, key)) ? "codex" : "generic";
12474
12536
  }
@@ -12946,8 +13008,8 @@ async function exportCommand(args) {
12946
13008
  }
12947
13009
  await assertHtmlFile(file);
12948
13010
  const absolute = await canonicalFile(file);
12949
- const root = path8.dirname(absolute);
12950
- const output = path8.resolve(flagValue(args, "--out") || path8.join(root, exportFileName(absolute)));
13011
+ const root = path9.dirname(absolute);
13012
+ const output = path9.resolve(flagValue(args, "--out") || path9.join(root, exportFileName(absolute)));
12951
13013
  const source = await readFile6(absolute, "utf8");
12952
13014
  const { html, warnings } = await buildSelfContainedHtml(source, {
12953
13015
  baseDir: root,
@@ -13002,7 +13064,7 @@ async function shareCommand(args) {
13002
13064
  }
13003
13065
  await assertHtmlFile(request.file);
13004
13066
  const absolute = await canonicalFile(request.file);
13005
- const root = path8.dirname(absolute);
13067
+ const root = path9.dirname(absolute);
13006
13068
  const source = await readFile6(absolute, "utf8");
13007
13069
  const { html, warnings } = await buildSelfContainedHtml(source, {
13008
13070
  baseDir: root,
@@ -13425,7 +13487,7 @@ async function setupPluginCommand() {
13425
13487
  const manifest = readPluginManifest(pluginRoot);
13426
13488
  if (!manifest) {
13427
13489
  throw new AxiError("No plugin.json found in the lavish-axi package", "SERVER_ERROR", [
13428
- `Expected a manifest at ${path8.join(pluginRoot, "plugin.json")}`,
13490
+ `Expected a manifest at ${path9.join(pluginRoot, "plugin.json")}`,
13429
13491
  "Reinstall lavish-axi, or run `npm run build:plugin` when working from a source checkout"
13430
13492
  ]);
13431
13493
  }
@@ -13449,7 +13511,7 @@ function collapseHome(target) {
13449
13511
  }
13450
13512
  function registerVsCodePlugin(pluginRoot, pluginName) {
13451
13513
  const settingsFile = resolveVsCodeSettingsFile(process.env, resolveHookHomeDir());
13452
- const settingsDir = path8.dirname(settingsFile);
13514
+ const settingsDir = path9.dirname(settingsFile);
13453
13515
  const hasSettingsFile = existsSync3(settingsFile);
13454
13516
  if (!hasSettingsFile && !existsSync3(settingsDir)) {
13455
13517
  return { client: "vscode", status: "absent", detail: "no VS Code user configuration found" };
@@ -13478,7 +13540,7 @@ function registerVsCodePlugin(pluginRoot, pluginName) {
13478
13540
  return { client: "vscode", status: "registered", detail: collapseHome(settingsFile) };
13479
13541
  }
13480
13542
  function registerCursorPlugin(pluginRoot, pluginName) {
13481
- const cursorDir = path8.join(resolveHookHomeDir(), ".cursor");
13543
+ const cursorDir = path9.join(resolveHookHomeDir(), ".cursor");
13482
13544
  if (!existsSync3(cursorDir)) {
13483
13545
  return { client: "cursor", status: "absent", detail: "no ~/.cursor directory found" };
13484
13546
  }
@@ -13551,9 +13613,9 @@ function parseCopilotPluginRecords(output) {
13551
13613
  }
13552
13614
  }
13553
13615
  function installedCopilotPluginSourcePath(pluginName) {
13554
- const configDir = process.env.COPILOT_HOME || path8.join(resolveHookHomeDir(), ".copilot");
13616
+ const configDir = process.env.COPILOT_HOME || path9.join(resolveHookHomeDir(), ".copilot");
13555
13617
  try {
13556
- const config = JSON.parse(readFileSync2(path8.join(configDir, "config.json"), "utf8"));
13618
+ const config = JSON.parse(readFileSync2(path9.join(configDir, "config.json"), "utf8"));
13557
13619
  const record = Array.isArray(config.installedPlugins) ? config.installedPlugins.find((candidate) => candidate?.name === pluginName) : null;
13558
13620
  return record ? copilotPluginSourcePath(record) : null;
13559
13621
  } catch {
@@ -13578,7 +13640,7 @@ function copilotPluginSourcePath(record) {
13578
13640
  continue;
13579
13641
  }
13580
13642
  }
13581
- if (path8.isAbsolute(candidate)) return candidate;
13643
+ if (path9.isAbsolute(candidate)) return candidate;
13582
13644
  }
13583
13645
  return null;
13584
13646
  }
@@ -13586,14 +13648,14 @@ function sameResolvedPath(left, right) {
13586
13648
  try {
13587
13649
  return realpathSync2(left) === realpathSync2(right);
13588
13650
  } catch {
13589
- return path8.resolve(left) === path8.resolve(right);
13651
+ return path9.resolve(left) === path9.resolve(right);
13590
13652
  }
13591
13653
  }
13592
13654
  function resolveHookHomeDir(env = process.env, fallback = os4.homedir()) {
13593
13655
  return env.HOME || fallback;
13594
13656
  }
13595
13657
  function resolveCopilotHookDir(env = process.env, homeDir = resolveHookHomeDir(env)) {
13596
- return path8.join(env.COPILOT_HOME || path8.join(homeDir, ".copilot"), "hooks");
13658
+ return path9.join(env.COPILOT_HOME || path9.join(homeDir, ".copilot"), "hooks");
13597
13659
  }
13598
13660
  function createCopilotCliAmbientContextScript(command = "lavish-axi") {
13599
13661
  return [
@@ -13642,9 +13704,9 @@ function installCopilotCliSessionStartHook({
13642
13704
  timeoutSec = 10,
13643
13705
  onError = void 0
13644
13706
  } = {}) {
13645
- const target = path8.join(hookDir, "lavish-axi.json");
13707
+ const target = path9.join(hookDir, "lavish-axi.json");
13646
13708
  try {
13647
- mkdirSync2(path8.dirname(target), { recursive: true });
13709
+ mkdirSync2(path9.dirname(target), { recursive: true });
13648
13710
  const current = existsSync3(target) ? JSON.parse(readFileSync2(target, "utf8")) : {};
13649
13711
  const [updated, changed] = computeCopilotCliHookUpdate(
13650
13712
  current,
@@ -13707,9 +13769,6 @@ async function assertHtmlFile(file) {
13707
13769
  ]);
13708
13770
  }
13709
13771
  }
13710
- function isHtmlPath(file) {
13711
- return file.toLowerCase().endsWith(".html") || file.toLowerCase().endsWith(".htm");
13712
- }
13713
13772
  var HEALTH_PROBE_TIMEOUT_MS = 500;
13714
13773
  var DEFAULT_HEALTH_TIMEOUT_MS = 2e3;
13715
13774
  var MAX_HEALTH_BODY_BYTES = 256 * 1024;
@@ -13922,7 +13981,7 @@ function serverReplacementReason(currentVersion, healthBody, forceRestart = fals
13922
13981
  }
13923
13982
  function shouldForceRestartForLocalBuild(executablePath, sourceServerExists = localSourceServerExists()) {
13924
13983
  const localBuildEntry = fileURLToPath4(new URL("../dist/cli.mjs", import.meta.url));
13925
- return sourceServerExists && path8.resolve(executablePath) === path8.resolve(localBuildEntry);
13984
+ return sourceServerExists && path9.resolve(executablePath) === path9.resolve(localBuildEntry);
13926
13985
  }
13927
13986
  function localSourceServerExists() {
13928
13987
  return existsSync3(fileURLToPath4(new URL("../src/server.js", import.meta.url)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lavish-axi",
3
- "version": "0.1.83",
3
+ "version": "0.1.84",
4
4
  "packageManager": "pnpm@11.1.1",
5
5
  "description": "HTML is the new markdown. Lavish is the new editor for your HTML artifacts.",
6
6
  "type": "module",
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "lavish-axi",
4
- "version": "0.1.83",
4
+ "version": "0.1.84",
5
5
  "description": "HTML is the new markdown. Lavish is the new editor for your HTML artifacts.",
6
6
  "author": {
7
7
  "name": "Kun Chen",