codeam-cli 2.67.7 → 2.68.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/index.js +398 -55
- package/package.json +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,13 @@ All notable changes to `codeam-cli` are documented here.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [2.67.6] — 2026-08-27
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **cli:** Ordena los POST del ciclo de vida del preview
|
|
12
|
+
- **cli:** Re-afirma el preview en el heartbeat para que su snapshot no caduque
|
|
13
|
+
|
|
7
14
|
## [2.67.5] — 2026-08-27
|
|
8
15
|
|
|
9
16
|
### Fixed
|
package/dist/index.js
CHANGED
|
@@ -8081,7 +8081,7 @@ function readAnonId() {
|
|
|
8081
8081
|
}
|
|
8082
8082
|
function superProperties() {
|
|
8083
8083
|
return {
|
|
8084
|
-
cliVersion: true ? "2.
|
|
8084
|
+
cliVersion: true ? "2.68.0" : "0.0.0-dev",
|
|
8085
8085
|
nodeVersion: process.version,
|
|
8086
8086
|
platform: process.platform,
|
|
8087
8087
|
arch: process.arch,
|
|
@@ -8323,7 +8323,7 @@ var http = __toESM(require("http"));
|
|
|
8323
8323
|
// package.json
|
|
8324
8324
|
var package_default = {
|
|
8325
8325
|
name: "codeam-cli",
|
|
8326
|
-
version: "2.
|
|
8326
|
+
version: "2.68.0",
|
|
8327
8327
|
description: "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device \u2014 async. The terminal companion for CodeAgent Mobile.",
|
|
8328
8328
|
type: "commonjs",
|
|
8329
8329
|
main: "dist/index.js",
|
|
@@ -8345,7 +8345,8 @@ var package_default = {
|
|
|
8345
8345
|
"test:coverage": "vitest run --coverage",
|
|
8346
8346
|
typecheck: "tsc --noEmit",
|
|
8347
8347
|
postinstall: "node dist/postinstall.js || true",
|
|
8348
|
-
prepublishOnly: "npm run build"
|
|
8348
|
+
prepublishOnly: "npm run build",
|
|
8349
|
+
e2e: "playwright test"
|
|
8349
8350
|
},
|
|
8350
8351
|
keywords: [
|
|
8351
8352
|
"claude",
|
|
@@ -8413,11 +8414,14 @@ var package_default = {
|
|
|
8413
8414
|
},
|
|
8414
8415
|
devDependencies: {
|
|
8415
8416
|
"@codeam/shared": "*",
|
|
8417
|
+
"@playwright/test": "^1.62.1",
|
|
8418
|
+
"@types/jsdom": "^30.0.0",
|
|
8416
8419
|
"@types/node": "^22.0.0",
|
|
8417
8420
|
"@types/qrcode-terminal": "^0.12.0",
|
|
8418
8421
|
"@types/which": "^3.0.4",
|
|
8419
8422
|
"@types/ws": "^8.5.0",
|
|
8420
8423
|
"@vitest/coverage-v8": "^4.1.7",
|
|
8424
|
+
jsdom: "^29.1.1",
|
|
8421
8425
|
"node-pty": "^1.1.0",
|
|
8422
8426
|
tsup: "^8.0.0",
|
|
8423
8427
|
typescript: "^7.0.2",
|
|
@@ -9855,7 +9859,7 @@ var CommandRelayService = class _CommandRelayService {
|
|
|
9855
9859
|
// fresh + clear the "CLI update available" banner after a self-update
|
|
9856
9860
|
// (a codespace that reinstalls @latest reconnects via heartbeat, not
|
|
9857
9861
|
// pair/reconnect). Older backends ignore the extra field.
|
|
9858
|
-
..."2.
|
|
9862
|
+
..."2.68.0" ? { ideVersion: "2.68.0" } : {}
|
|
9859
9863
|
}).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
|
|
9860
9864
|
}
|
|
9861
9865
|
/**
|
|
@@ -14058,7 +14062,7 @@ function subscribeToPairCompletion(pluginId, onPaired, onTimeout, pollSecret) {
|
|
|
14058
14062
|
onPaired(info);
|
|
14059
14063
|
};
|
|
14060
14064
|
dispatchers.add(dispatch);
|
|
14061
|
-
const
|
|
14065
|
+
const connect3 = () => {
|
|
14062
14066
|
if (stopped) return;
|
|
14063
14067
|
const url2 = new URL(`${API_BASE5}/api/commands/pending/stream`);
|
|
14064
14068
|
url2.searchParams.set("pluginId", pluginId);
|
|
@@ -14084,7 +14088,7 @@ function subscribeToPairCompletion(pluginId, onPaired, onTimeout, pollSecret) {
|
|
|
14084
14088
|
`sse status=${res.statusCode}; will reconnect in 1s`
|
|
14085
14089
|
);
|
|
14086
14090
|
res.resume();
|
|
14087
|
-
setTimeout(
|
|
14091
|
+
setTimeout(connect3, 1e3);
|
|
14088
14092
|
return;
|
|
14089
14093
|
}
|
|
14090
14094
|
let buf = "";
|
|
@@ -14099,23 +14103,23 @@ function subscribeToPairCompletion(pluginId, onPaired, onTimeout, pollSecret) {
|
|
|
14099
14103
|
}
|
|
14100
14104
|
});
|
|
14101
14105
|
res.on("end", () => {
|
|
14102
|
-
if (!stopped) setTimeout(
|
|
14106
|
+
if (!stopped) setTimeout(connect3, 250);
|
|
14103
14107
|
});
|
|
14104
14108
|
res.on("error", () => {
|
|
14105
|
-
if (!stopped) setTimeout(
|
|
14109
|
+
if (!stopped) setTimeout(connect3, 1e3);
|
|
14106
14110
|
});
|
|
14107
14111
|
}
|
|
14108
14112
|
);
|
|
14109
14113
|
req.on("error", () => {
|
|
14110
|
-
if (!stopped) setTimeout(
|
|
14114
|
+
if (!stopped) setTimeout(connect3, 1e3);
|
|
14111
14115
|
});
|
|
14112
14116
|
req.on("timeout", () => {
|
|
14113
14117
|
req?.destroy();
|
|
14114
|
-
if (!stopped) setTimeout(
|
|
14118
|
+
if (!stopped) setTimeout(connect3, 250);
|
|
14115
14119
|
});
|
|
14116
14120
|
req.end();
|
|
14117
14121
|
};
|
|
14118
|
-
|
|
14122
|
+
connect3();
|
|
14119
14123
|
timeoutTimer = setTimeout(() => {
|
|
14120
14124
|
stop();
|
|
14121
14125
|
onTimeout();
|
|
@@ -22114,7 +22118,7 @@ async function autoUpgradeBeforeCriticalCommand() {
|
|
|
22114
22118
|
if (process.env.NODE_ENV === "test") return;
|
|
22115
22119
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
22116
22120
|
if (process.env.CI) return;
|
|
22117
|
-
const current2 = true ? "2.
|
|
22121
|
+
const current2 = true ? "2.68.0" : null;
|
|
22118
22122
|
if (!current2) return;
|
|
22119
22123
|
const cache = readCache();
|
|
22120
22124
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -22131,7 +22135,7 @@ function checkForUpdates() {
|
|
|
22131
22135
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
22132
22136
|
if (process.env.CI) return;
|
|
22133
22137
|
if (!process.stdout.isTTY) return;
|
|
22134
|
-
const current2 = true ? "2.
|
|
22138
|
+
const current2 = true ? "2.68.0" : null;
|
|
22135
22139
|
if (!current2) return;
|
|
22136
22140
|
const cache = readCache();
|
|
22137
22141
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -22154,7 +22158,7 @@ var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
|
|
|
22154
22158
|
var SELF_UPDATE_LS_TIMEOUT_MS = 15e3;
|
|
22155
22159
|
var SUDO_PREFLIGHT_TIMEOUT_MS = 5e3;
|
|
22156
22160
|
function currentCliVersion() {
|
|
22157
|
-
return true ? "2.
|
|
22161
|
+
return true ? "2.68.0" : null;
|
|
22158
22162
|
}
|
|
22159
22163
|
async function installedVersion(deps) {
|
|
22160
22164
|
const ls = await deps.run(
|
|
@@ -25103,7 +25107,7 @@ function registerPreview(sessionId, preview) {
|
|
|
25103
25107
|
}
|
|
25104
25108
|
function killProcessTree(child, signal = "SIGTERM") {
|
|
25105
25109
|
const pid = child.pid;
|
|
25106
|
-
if (pid
|
|
25110
|
+
if (!pid) return;
|
|
25107
25111
|
if (process.platform !== "win32") {
|
|
25108
25112
|
try {
|
|
25109
25113
|
process.kill(-pid, signal);
|
|
@@ -25125,6 +25129,10 @@ async function killPreview(sessionId) {
|
|
|
25125
25129
|
killProcessTree(preview.tunnel, "SIGTERM");
|
|
25126
25130
|
}
|
|
25127
25131
|
await new Promise((r) => setTimeout(r, 100));
|
|
25132
|
+
try {
|
|
25133
|
+
await preview.inspector?.close();
|
|
25134
|
+
} catch {
|
|
25135
|
+
}
|
|
25128
25136
|
killProcessTree(preview.devServer, "SIGTERM");
|
|
25129
25137
|
const sigkillTimer = setTimeout(() => {
|
|
25130
25138
|
killProcessTree(preview.devServer, "SIGKILL");
|
|
@@ -25309,6 +25317,336 @@ async function resolveNamedTunnel(ctx, fetchTunnel) {
|
|
|
25309
25317
|
}
|
|
25310
25318
|
}
|
|
25311
25319
|
|
|
25320
|
+
// src/services/preview/inspector-proxy.ts
|
|
25321
|
+
var http6 = __toESM(require("http"));
|
|
25322
|
+
var net3 = __toESM(require("net"));
|
|
25323
|
+
var INJECTION_SCAN_BUDGET_BYTES = 64 * 1024;
|
|
25324
|
+
var INJECTION_ANCHORS = ["</head>", "<body>", "</body>"];
|
|
25325
|
+
function wantsHtml(accept) {
|
|
25326
|
+
return typeof accept === "string" && accept.includes("text/html");
|
|
25327
|
+
}
|
|
25328
|
+
function isHtmlResponse(contentType) {
|
|
25329
|
+
return typeof contentType === "string" && contentType.toLowerCase().includes("text/html");
|
|
25330
|
+
}
|
|
25331
|
+
function injectAt(html, script) {
|
|
25332
|
+
for (const anchor of INJECTION_ANCHORS) {
|
|
25333
|
+
const at3 = html.indexOf(anchor);
|
|
25334
|
+
if (at3 === -1) continue;
|
|
25335
|
+
const insertAt = anchor.startsWith("</") ? at3 : at3 + anchor.length;
|
|
25336
|
+
return html.slice(0, insertAt) + script + html.slice(insertAt);
|
|
25337
|
+
}
|
|
25338
|
+
return null;
|
|
25339
|
+
}
|
|
25340
|
+
async function startInspectorProxy(opts) {
|
|
25341
|
+
const targetHost = opts.targetHost ?? "127.0.0.1";
|
|
25342
|
+
const upgraded = /* @__PURE__ */ new Set();
|
|
25343
|
+
const server = http6.createServer((req, res) => {
|
|
25344
|
+
const inject = wantsHtml(req.headers.accept);
|
|
25345
|
+
const headers = { ...req.headers };
|
|
25346
|
+
if (inject) delete headers["accept-encoding"];
|
|
25347
|
+
const upstream = http6.request(
|
|
25348
|
+
{ host: targetHost, port: opts.targetPort, method: req.method, path: req.url, headers },
|
|
25349
|
+
(originRes) => {
|
|
25350
|
+
const outHeaders = { ...originRes.headers };
|
|
25351
|
+
const willInject = inject && isHtmlResponse(originRes.headers["content-type"]);
|
|
25352
|
+
if (willInject) delete outHeaders["content-length"];
|
|
25353
|
+
res.writeHead(originRes.statusCode ?? 502, outHeaders);
|
|
25354
|
+
if (!willInject) {
|
|
25355
|
+
originRes.pipe(res);
|
|
25356
|
+
return;
|
|
25357
|
+
}
|
|
25358
|
+
let buffered = "";
|
|
25359
|
+
let done = false;
|
|
25360
|
+
originRes.setEncoding("utf8");
|
|
25361
|
+
originRes.on("data", (chunk) => {
|
|
25362
|
+
if (done) {
|
|
25363
|
+
res.write(chunk);
|
|
25364
|
+
return;
|
|
25365
|
+
}
|
|
25366
|
+
buffered += chunk;
|
|
25367
|
+
const injected = injectAt(buffered, opts.script);
|
|
25368
|
+
if (injected !== null) {
|
|
25369
|
+
done = true;
|
|
25370
|
+
res.write(injected);
|
|
25371
|
+
buffered = "";
|
|
25372
|
+
return;
|
|
25373
|
+
}
|
|
25374
|
+
if (buffered.length >= INJECTION_SCAN_BUDGET_BYTES) {
|
|
25375
|
+
done = true;
|
|
25376
|
+
res.write(buffered);
|
|
25377
|
+
buffered = "";
|
|
25378
|
+
}
|
|
25379
|
+
});
|
|
25380
|
+
originRes.on("end", () => {
|
|
25381
|
+
if (!done && buffered.length > 0) res.write(buffered);
|
|
25382
|
+
res.end();
|
|
25383
|
+
});
|
|
25384
|
+
}
|
|
25385
|
+
);
|
|
25386
|
+
upstream.on("error", () => {
|
|
25387
|
+
if (!res.headersSent) res.writeHead(502, { "content-type": "text/plain" });
|
|
25388
|
+
res.end("preview origin unreachable");
|
|
25389
|
+
});
|
|
25390
|
+
req.pipe(upstream);
|
|
25391
|
+
});
|
|
25392
|
+
server.on("upgrade", (req, clientSocket, head) => {
|
|
25393
|
+
upgraded.add(clientSocket);
|
|
25394
|
+
const upstream = net3.connect(opts.targetPort, targetHost, () => {
|
|
25395
|
+
const lines = [`${req.method} ${req.url} HTTP/1.1`];
|
|
25396
|
+
for (const [k2, v] of Object.entries(req.headers)) {
|
|
25397
|
+
for (const one of Array.isArray(v) ? v : [v]) {
|
|
25398
|
+
if (one !== void 0) lines.push(`${k2}: ${one}`);
|
|
25399
|
+
}
|
|
25400
|
+
}
|
|
25401
|
+
upstream.write(`${lines.join("\r\n")}\r
|
|
25402
|
+
\r
|
|
25403
|
+
`);
|
|
25404
|
+
if (head?.length) upstream.write(head);
|
|
25405
|
+
upstream.pipe(clientSocket);
|
|
25406
|
+
clientSocket.pipe(upstream);
|
|
25407
|
+
});
|
|
25408
|
+
const drop = () => {
|
|
25409
|
+
upgraded.delete(clientSocket);
|
|
25410
|
+
upstream.destroy();
|
|
25411
|
+
clientSocket.destroy();
|
|
25412
|
+
};
|
|
25413
|
+
upstream.on("error", drop);
|
|
25414
|
+
upstream.on("close", drop);
|
|
25415
|
+
clientSocket.on("error", drop);
|
|
25416
|
+
clientSocket.on("close", drop);
|
|
25417
|
+
});
|
|
25418
|
+
await new Promise((resolve10, reject) => {
|
|
25419
|
+
server.once("error", reject);
|
|
25420
|
+
server.listen(0, "127.0.0.1", () => resolve10());
|
|
25421
|
+
});
|
|
25422
|
+
const port = server.address().port;
|
|
25423
|
+
return {
|
|
25424
|
+
port,
|
|
25425
|
+
close: () => new Promise((resolve10) => {
|
|
25426
|
+
for (const socket of upgraded) socket.destroy();
|
|
25427
|
+
upgraded.clear();
|
|
25428
|
+
server.closeAllConnections?.();
|
|
25429
|
+
server.close(() => resolve10());
|
|
25430
|
+
})
|
|
25431
|
+
};
|
|
25432
|
+
}
|
|
25433
|
+
|
|
25434
|
+
// src/services/preview/inspector-client.ts
|
|
25435
|
+
var INSPECTOR_CHANNEL = "codeam-inspector";
|
|
25436
|
+
function inspectorClientSource(opts) {
|
|
25437
|
+
const origins = JSON.stringify(opts.allowedOrigins);
|
|
25438
|
+
const channel = JSON.stringify(INSPECTOR_CHANNEL);
|
|
25439
|
+
return `<script id="codeam-inspector" data-codeam="inspector">
|
|
25440
|
+
(function () {
|
|
25441
|
+
'use strict';
|
|
25442
|
+
try {
|
|
25443
|
+
var CHANNEL = ${channel};
|
|
25444
|
+
var ALLOWED = ${origins};
|
|
25445
|
+
|
|
25446
|
+
var enabled = false;
|
|
25447
|
+
var driver = null; // el origen que nos encendio
|
|
25448
|
+
var overlay = null;
|
|
25449
|
+
var label = null;
|
|
25450
|
+
var current = null;
|
|
25451
|
+
|
|
25452
|
+
function allowed(origin) {
|
|
25453
|
+
return ALLOWED.indexOf(origin) !== -1;
|
|
25454
|
+
}
|
|
25455
|
+
|
|
25456
|
+
function send(type, payload) {
|
|
25457
|
+
if (!driver || window.parent === window) return;
|
|
25458
|
+
var msg = { source: CHANNEL, type: type };
|
|
25459
|
+
for (var k in payload) if (Object.prototype.hasOwnProperty.call(payload, k)) msg[k] = payload[k];
|
|
25460
|
+
window.parent.postMessage(msg, driver);
|
|
25461
|
+
}
|
|
25462
|
+
|
|
25463
|
+
// El resaltado. Un div fijo por encima de todo que NUNCA captura el
|
|
25464
|
+
// puntero: si lo capturara, el elemento de debajo dejaria de recibir el
|
|
25465
|
+
// mousemove y el resaltado se quedaria pegado al primero que tocara.
|
|
25466
|
+
function ensureOverlay() {
|
|
25467
|
+
if (overlay) return;
|
|
25468
|
+
overlay = document.createElement('div');
|
|
25469
|
+
overlay.setAttribute('data-codeam', 'inspector-overlay');
|
|
25470
|
+
overlay.style.cssText = [
|
|
25471
|
+
'position:fixed', 'pointer-events:none', 'z-index:2147483647',
|
|
25472
|
+
'border:1px solid #a855f7', 'background:rgba(168,85,247,0.14)',
|
|
25473
|
+
'border-radius:2px', 'transition:none', 'display:none'
|
|
25474
|
+
].join(';');
|
|
25475
|
+
label = document.createElement('div');
|
|
25476
|
+
label.setAttribute('data-codeam', 'inspector-label');
|
|
25477
|
+
label.style.cssText = [
|
|
25478
|
+
'position:fixed', 'pointer-events:none', 'z-index:2147483647',
|
|
25479
|
+
'background:#a855f7', 'color:#000', 'font:600 11px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace',
|
|
25480
|
+
'padding:2px 6px', 'border-radius:3px', 'white-space:nowrap', 'display:none'
|
|
25481
|
+
].join(';');
|
|
25482
|
+
document.body.appendChild(overlay);
|
|
25483
|
+
document.body.appendChild(label);
|
|
25484
|
+
}
|
|
25485
|
+
|
|
25486
|
+
function removeOverlay() {
|
|
25487
|
+
if (overlay && overlay.parentNode) overlay.parentNode.removeChild(overlay);
|
|
25488
|
+
if (label && label.parentNode) label.parentNode.removeChild(label);
|
|
25489
|
+
overlay = null;
|
|
25490
|
+
label = null;
|
|
25491
|
+
}
|
|
25492
|
+
|
|
25493
|
+
// Fichero y linea del componente de React, cuando el dev server los
|
|
25494
|
+
// expone. Es lo que hace que el agente reciba
|
|
25495
|
+
// "components/whatsapp-button.tsx:42" en vez de "x:0.62 y:0.81".
|
|
25496
|
+
// Solo existe en builds de desarrollo; en produccion devuelve null y se
|
|
25497
|
+
// cae al selector CSS.
|
|
25498
|
+
function reactSource(el) {
|
|
25499
|
+
try {
|
|
25500
|
+
for (var key in el) {
|
|
25501
|
+
if (key.indexOf('__reactFiber$') !== 0) continue;
|
|
25502
|
+
var fiber = el[key];
|
|
25503
|
+
while (fiber) {
|
|
25504
|
+
var src = fiber._debugSource;
|
|
25505
|
+
if (src && src.fileName) {
|
|
25506
|
+
return { file: String(src.fileName), line: Number(src.lineNumber) || null };
|
|
25507
|
+
}
|
|
25508
|
+
fiber = fiber.return;
|
|
25509
|
+
}
|
|
25510
|
+
}
|
|
25511
|
+
} catch (e) { /* nunca romper la pagina por esto */ }
|
|
25512
|
+
return null;
|
|
25513
|
+
}
|
|
25514
|
+
|
|
25515
|
+
// Un selector corto y legible, el respaldo cuando no hay React.
|
|
25516
|
+
function selectorFor(el) {
|
|
25517
|
+
if (el.id) return '#' + el.id;
|
|
25518
|
+
var name = el.tagName ? el.tagName.toLowerCase() : 'node';
|
|
25519
|
+
var cls = (el.className && typeof el.className === 'string')
|
|
25520
|
+
? el.className.trim().split(/\\s+/).slice(0, 3).join('.')
|
|
25521
|
+
: '';
|
|
25522
|
+
return cls ? name + '.' + cls : name;
|
|
25523
|
+
}
|
|
25524
|
+
|
|
25525
|
+
function describe(el) {
|
|
25526
|
+
var r = el.getBoundingClientRect();
|
|
25527
|
+
var text = (el.textContent || '').replace(/\\s+/g, ' ').trim();
|
|
25528
|
+
return {
|
|
25529
|
+
tag: el.tagName ? el.tagName.toLowerCase() : 'node',
|
|
25530
|
+
id: el.id || null,
|
|
25531
|
+
classes: (el.className && typeof el.className === 'string') ? el.className.trim() : '',
|
|
25532
|
+
selector: selectorFor(el),
|
|
25533
|
+
// Un recorte, no el contenido: describir no es extraer.
|
|
25534
|
+
text: text.length > 120 ? text.slice(0, 120) + '\u2026' : text,
|
|
25535
|
+
rect: { x: r.left, y: r.top, w: r.width, h: r.height },
|
|
25536
|
+
viewport: { w: window.innerWidth, h: window.innerHeight },
|
|
25537
|
+
source: reactSource(el)
|
|
25538
|
+
};
|
|
25539
|
+
}
|
|
25540
|
+
|
|
25541
|
+
function paint(el) {
|
|
25542
|
+
ensureOverlay();
|
|
25543
|
+
var r = el.getBoundingClientRect();
|
|
25544
|
+
overlay.style.display = 'block';
|
|
25545
|
+
overlay.style.left = r.left + 'px';
|
|
25546
|
+
overlay.style.top = r.top + 'px';
|
|
25547
|
+
overlay.style.width = r.width + 'px';
|
|
25548
|
+
overlay.style.height = r.height + 'px';
|
|
25549
|
+
label.style.display = 'block';
|
|
25550
|
+
label.textContent = selectorFor(el) + ' ' + Math.round(r.width) + '\xD7' + Math.round(r.height);
|
|
25551
|
+
// Encima si cabe; si no, dentro. Una etiqueta cortada por el borde
|
|
25552
|
+
// superior no se lee.
|
|
25553
|
+
var above = r.top >= 20;
|
|
25554
|
+
label.style.left = r.left + 'px';
|
|
25555
|
+
label.style.top = (above ? r.top - 20 : r.top + 2) + 'px';
|
|
25556
|
+
}
|
|
25557
|
+
|
|
25558
|
+
function onMove(e) {
|
|
25559
|
+
if (!enabled) return;
|
|
25560
|
+
var el = e.target;
|
|
25561
|
+
if (!el || el === current || !el.getBoundingClientRect) return;
|
|
25562
|
+
if (el.getAttribute && el.getAttribute('data-codeam')) return;
|
|
25563
|
+
current = el;
|
|
25564
|
+
paint(el);
|
|
25565
|
+
send('hover', { element: describe(el) });
|
|
25566
|
+
}
|
|
25567
|
+
|
|
25568
|
+
// Captura, y se traga el evento: mientras el modo esta activo un clic
|
|
25569
|
+
// SELECCIONA, no navega. Sin esto, pulsar un enlace se llevaria el
|
|
25570
|
+
// preview a otra pagina en mitad del gesto.
|
|
25571
|
+
function onClick(e) {
|
|
25572
|
+
if (!enabled) return;
|
|
25573
|
+
var el = e.target;
|
|
25574
|
+
if (el && el.getAttribute && el.getAttribute('data-codeam')) return;
|
|
25575
|
+
e.preventDefault();
|
|
25576
|
+
e.stopPropagation();
|
|
25577
|
+
if (el) send('pick', { element: describe(el) });
|
|
25578
|
+
}
|
|
25579
|
+
|
|
25580
|
+
function enable(origin) {
|
|
25581
|
+
if (enabled) return;
|
|
25582
|
+
enabled = true;
|
|
25583
|
+
driver = origin;
|
|
25584
|
+
document.addEventListener('mousemove', onMove, true);
|
|
25585
|
+
document.addEventListener('click', onClick, true);
|
|
25586
|
+
send('ready', {});
|
|
25587
|
+
}
|
|
25588
|
+
|
|
25589
|
+
function disable() {
|
|
25590
|
+
if (!enabled) return;
|
|
25591
|
+
enabled = false;
|
|
25592
|
+
current = null;
|
|
25593
|
+
document.removeEventListener('mousemove', onMove, true);
|
|
25594
|
+
document.removeEventListener('click', onClick, true);
|
|
25595
|
+
removeOverlay();
|
|
25596
|
+
driver = null;
|
|
25597
|
+
}
|
|
25598
|
+
|
|
25599
|
+
window.addEventListener('message', function (e) {
|
|
25600
|
+
if (!allowed(e.origin)) return;
|
|
25601
|
+
var data = e.data;
|
|
25602
|
+
if (!data || data.source !== CHANNEL) return;
|
|
25603
|
+
if (data.type === 'enable') enable(e.origin);
|
|
25604
|
+
else if (data.type === 'disable') disable();
|
|
25605
|
+
});
|
|
25606
|
+
} catch (e) {
|
|
25607
|
+
// Pase lo que pase, la app del usuario sigue funcionando.
|
|
25608
|
+
}
|
|
25609
|
+
})();
|
|
25610
|
+
</script>`;
|
|
25611
|
+
}
|
|
25612
|
+
|
|
25613
|
+
// src/services/preview/inspector-bringup.ts
|
|
25614
|
+
var DEFAULT_INSPECTOR_ORIGINS = [
|
|
25615
|
+
"https://www.codeagent-mobile.com",
|
|
25616
|
+
"https://codeagent-mobile.com",
|
|
25617
|
+
"https://dev.codeagent-mobile.com"
|
|
25618
|
+
];
|
|
25619
|
+
function inspectorEnabled(env) {
|
|
25620
|
+
const raw = env.CODEAM_PREVIEW_INSPECTOR;
|
|
25621
|
+
return raw !== "0" && raw !== "false";
|
|
25622
|
+
}
|
|
25623
|
+
function resolveInspectorOrigins(env) {
|
|
25624
|
+
const raw = env.CODEAM_INSPECTOR_ORIGINS;
|
|
25625
|
+
if (!raw) return DEFAULT_INSPECTOR_ORIGINS;
|
|
25626
|
+
const extra = raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
25627
|
+
return [...DEFAULT_INSPECTOR_ORIGINS, ...extra];
|
|
25628
|
+
}
|
|
25629
|
+
async function bringUpInspector(targetPort, deps = {}) {
|
|
25630
|
+
const env = deps.env ?? process.env;
|
|
25631
|
+
const log2 = deps.log ?? (() => void 0);
|
|
25632
|
+
if (!inspectorEnabled(env)) {
|
|
25633
|
+
log2("preview inspector disabled by CODEAM_PREVIEW_INSPECTOR");
|
|
25634
|
+
return { port: targetPort, proxy: null };
|
|
25635
|
+
}
|
|
25636
|
+
try {
|
|
25637
|
+
const start2 = deps.start ?? startInspectorProxy;
|
|
25638
|
+
const proxy = await start2({
|
|
25639
|
+
targetPort,
|
|
25640
|
+
script: inspectorClientSource({ allowedOrigins: resolveInspectorOrigins(env) })
|
|
25641
|
+
});
|
|
25642
|
+
log2(`preview inspector proxy on :${proxy.port} \u2192 :${targetPort}`);
|
|
25643
|
+
return { port: proxy.port, proxy };
|
|
25644
|
+
} catch (e) {
|
|
25645
|
+
log2(`preview inspector unavailable (${e.message}) \u2014 going direct`);
|
|
25646
|
+
return { port: targetPort, proxy: null };
|
|
25647
|
+
}
|
|
25648
|
+
}
|
|
25649
|
+
|
|
25312
25650
|
// src/services/preview/start-orchestrator.ts
|
|
25313
25651
|
var INSTALL_TIMEOUT_MS2 = 5 * 6e4;
|
|
25314
25652
|
var SETUP_TIMEOUT_MS = 2 * 6e4;
|
|
@@ -25420,6 +25758,7 @@ async function runPreviewStart(args2) {
|
|
|
25420
25758
|
sessionId,
|
|
25421
25759
|
devServer: dev.devServer,
|
|
25422
25760
|
tunnel: tun.tunnel,
|
|
25761
|
+
inspector: tun.inspector,
|
|
25423
25762
|
url: tun.url,
|
|
25424
25763
|
framework: detection.framework,
|
|
25425
25764
|
detection,
|
|
@@ -25665,8 +26004,12 @@ async function establishTunnel(ctx, dev) {
|
|
|
25665
26004
|
});
|
|
25666
26005
|
return null;
|
|
25667
26006
|
}
|
|
25668
|
-
return { url: expoUrl, tunnel: null };
|
|
26007
|
+
return { url: expoUrl, tunnel: null, inspector: null };
|
|
25669
26008
|
}
|
|
26009
|
+
const inspection = await bringUpInspector(detection.port, {
|
|
26010
|
+
log: (m) => log.info("preview", m)
|
|
26011
|
+
});
|
|
26012
|
+
const tunnelPort = inspection.port;
|
|
25670
26013
|
let bin;
|
|
25671
26014
|
try {
|
|
25672
26015
|
bin = await resolveCloudflared();
|
|
@@ -25694,7 +26037,7 @@ async function establishTunnel(ctx, dev) {
|
|
|
25694
26037
|
if (namedToken && namedHostname) {
|
|
25695
26038
|
try {
|
|
25696
26039
|
emitProgress("TUNNEL_STARTING", `named tunnel ${namedHostname}`);
|
|
25697
|
-
const candidate = await spawnNamedTunnel(bin, namedToken,
|
|
26040
|
+
const candidate = await spawnNamedTunnel(bin, namedToken, tunnelPort);
|
|
25698
26041
|
const outcome = await awaitTunnelRegistered(
|
|
25699
26042
|
candidate,
|
|
25700
26043
|
namedHostname,
|
|
@@ -25727,7 +26070,7 @@ async function establishTunnel(ctx, dev) {
|
|
|
25727
26070
|
}
|
|
25728
26071
|
const candidate = (0, import_child_process22.spawn)(
|
|
25729
26072
|
bin,
|
|
25730
|
-
["tunnel", "--url", `http://localhost:${
|
|
26073
|
+
["tunnel", "--url", `http://localhost:${tunnelPort}`],
|
|
25731
26074
|
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
25732
26075
|
);
|
|
25733
26076
|
const outcome = await awaitTunnelRegistered(
|
|
@@ -25752,7 +26095,7 @@ async function establishTunnel(ctx, dev) {
|
|
|
25752
26095
|
});
|
|
25753
26096
|
return null;
|
|
25754
26097
|
}
|
|
25755
|
-
return { url: parsedUrl, tunnel };
|
|
26098
|
+
return { url: parsedUrl, tunnel, inspector: inspection.proxy };
|
|
25756
26099
|
}
|
|
25757
26100
|
|
|
25758
26101
|
// src/beads/bd-adapter.ts
|
|
@@ -29842,7 +30185,7 @@ var fs67 = __toESM(require("fs"));
|
|
|
29842
30185
|
var path74 = __toESM(require("path"));
|
|
29843
30186
|
var os56 = __toESM(require("os"));
|
|
29844
30187
|
var https7 = __toESM(require("https"));
|
|
29845
|
-
var
|
|
30188
|
+
var http7 = __toESM(require("http"));
|
|
29846
30189
|
var import_zod2 = require("zod");
|
|
29847
30190
|
var historyRecordSchema = import_zod2.z.object({
|
|
29848
30191
|
type: import_zod2.z.string().optional(),
|
|
@@ -29925,7 +30268,7 @@ function post(endpoint, body, pluginAuthToken) {
|
|
|
29925
30268
|
return new Promise((resolve10) => {
|
|
29926
30269
|
const payload = JSON.stringify(body);
|
|
29927
30270
|
const u2 = new URL(`${API_BASE8}${endpoint}`);
|
|
29928
|
-
const transport = u2.protocol === "https:" ? https7 :
|
|
30271
|
+
const transport = u2.protocol === "https:" ? https7 : http7;
|
|
29929
30272
|
const req = transport.request(
|
|
29930
30273
|
{
|
|
29931
30274
|
hostname: u2.hostname,
|
|
@@ -32822,8 +33165,8 @@ var ConnectionBuilder = class {
|
|
|
32822
33165
|
if (message.kind !== "request" || message.method !== method) {
|
|
32823
33166
|
return Handled.no(message);
|
|
32824
33167
|
}
|
|
32825
|
-
const
|
|
32826
|
-
return await handler(
|
|
33168
|
+
const request2 = parse(message.params);
|
|
33169
|
+
return await handler(request2, message.responder, cx) ?? Handled.yes();
|
|
32827
33170
|
},
|
|
32828
33171
|
describe: () => `${this.connectionName ?? "request"}:${method}`
|
|
32829
33172
|
});
|
|
@@ -33384,23 +33727,23 @@ var AsyncQueue = class {
|
|
|
33384
33727
|
});
|
|
33385
33728
|
}
|
|
33386
33729
|
};
|
|
33387
|
-
function cloneNewSessionRequest(
|
|
33730
|
+
function cloneNewSessionRequest(request2) {
|
|
33388
33731
|
return {
|
|
33389
|
-
...
|
|
33390
|
-
additionalDirectories:
|
|
33391
|
-
mcpServers: [...
|
|
33732
|
+
...request2,
|
|
33733
|
+
additionalDirectories: request2.additionalDirectories ? [...request2.additionalDirectories] : void 0,
|
|
33734
|
+
mcpServers: [...request2.mcpServers]
|
|
33392
33735
|
};
|
|
33393
33736
|
}
|
|
33394
33737
|
var SessionBuilder = class _SessionBuilder {
|
|
33395
33738
|
cx;
|
|
33396
33739
|
request;
|
|
33397
|
-
constructor(cx,
|
|
33740
|
+
constructor(cx, request2) {
|
|
33398
33741
|
this.cx = cx;
|
|
33399
|
-
this.request = cloneNewSessionRequest(
|
|
33742
|
+
this.request = cloneNewSessionRequest(request2);
|
|
33400
33743
|
}
|
|
33401
33744
|
/** @internal */
|
|
33402
|
-
static create(cx,
|
|
33403
|
-
return new _SessionBuilder(cx,
|
|
33745
|
+
static create(cx, request2) {
|
|
33746
|
+
return new _SessionBuilder(cx, request2);
|
|
33404
33747
|
}
|
|
33405
33748
|
/**
|
|
33406
33749
|
* Returns the `session/new` request that will be sent.
|
|
@@ -34528,9 +34871,9 @@ function toolCallReferencesInternal(call, homeDir2) {
|
|
|
34528
34871
|
}
|
|
34529
34872
|
return false;
|
|
34530
34873
|
}
|
|
34531
|
-
function internalPathPermissionOutcome(
|
|
34532
|
-
if (!toolCallReferencesInternal(
|
|
34533
|
-
const reject =
|
|
34874
|
+
function internalPathPermissionOutcome(request2, homeDir2) {
|
|
34875
|
+
if (!toolCallReferencesInternal(request2.toolCall, homeDir2)) return null;
|
|
34876
|
+
const reject = request2.options.find((o) => o.kind === "reject_once") ?? request2.options.find((o) => o.kind === "reject_always");
|
|
34534
34877
|
if (reject) return { outcome: { outcome: "selected", optionId: reject.optionId } };
|
|
34535
34878
|
return { outcome: { outcome: "cancelled" } };
|
|
34536
34879
|
}
|
|
@@ -34589,9 +34932,9 @@ function toolPathIsSecret(p2) {
|
|
|
34589
34932
|
return !!p2 && SECRET_RE.test(p2);
|
|
34590
34933
|
}
|
|
34591
34934
|
var GUARDRAIL_SECRET_READ_BLOCK_REASON = "Reading this secret file is blocked by this session's guardrails (Reading secrets = Deny). Adjust it in the session Guardrails settings if intended.";
|
|
34592
|
-
function guardrailDecision(
|
|
34593
|
-
if (isProseOnlyToolKind(
|
|
34594
|
-
const hay = haystack(
|
|
34935
|
+
function guardrailDecision(request2, policy) {
|
|
34936
|
+
if (isProseOnlyToolKind(request2.toolCall.kind)) return null;
|
|
34937
|
+
const hay = haystack(request2.toolCall);
|
|
34595
34938
|
if (!hay) return null;
|
|
34596
34939
|
const matched = matchedCategories(hay);
|
|
34597
34940
|
if (matched.size === 0) return null;
|
|
@@ -34611,7 +34954,7 @@ function guardrailDecision(request, policy) {
|
|
|
34611
34954
|
kind: "deny",
|
|
34612
34955
|
category: denyCat,
|
|
34613
34956
|
reason: GUARDRAIL_CATEGORY_META[denyCat].description,
|
|
34614
|
-
outcome: rejectOutcome(
|
|
34957
|
+
outcome: rejectOutcome(request2.options)
|
|
34615
34958
|
};
|
|
34616
34959
|
}
|
|
34617
34960
|
if (confirmCat) {
|
|
@@ -35876,7 +36219,7 @@ async function performAgentSwitch(deps, rawAgentId, fastPath = {}) {
|
|
|
35876
36219
|
}
|
|
35877
36220
|
|
|
35878
36221
|
// src/services/streaming/transport.ts
|
|
35879
|
-
var
|
|
36222
|
+
var http8 = __toESM(require("http"));
|
|
35880
36223
|
var https8 = __toESM(require("https"));
|
|
35881
36224
|
var _transport4 = {
|
|
35882
36225
|
post: _post3,
|
|
@@ -35886,7 +36229,7 @@ function _post3(url2, headers, payload) {
|
|
|
35886
36229
|
return new Promise((resolve10, reject) => {
|
|
35887
36230
|
let settled = false;
|
|
35888
36231
|
const u2 = new URL(url2);
|
|
35889
|
-
const lib = u2.protocol === "https:" ? https8 :
|
|
36232
|
+
const lib = u2.protocol === "https:" ? https8 : http8;
|
|
35890
36233
|
const req = lib.request(
|
|
35891
36234
|
{
|
|
35892
36235
|
hostname: u2.hostname,
|
|
@@ -35928,7 +36271,7 @@ function _get(url2, headers) {
|
|
|
35928
36271
|
return new Promise((resolve10, reject) => {
|
|
35929
36272
|
let settled = false;
|
|
35930
36273
|
const u2 = new URL(url2);
|
|
35931
|
-
const lib = u2.protocol === "https:" ? https8 :
|
|
36274
|
+
const lib = u2.protocol === "https:" ? https8 : http8;
|
|
35932
36275
|
const req = lib.request(
|
|
35933
36276
|
{
|
|
35934
36277
|
hostname: u2.hostname,
|
|
@@ -36733,12 +37076,12 @@ function mapSessionUpdate(notification) {
|
|
|
36733
37076
|
return [];
|
|
36734
37077
|
}
|
|
36735
37078
|
}
|
|
36736
|
-
function mapPermissionRequest(
|
|
36737
|
-
const prompt = describeToolCall(
|
|
37079
|
+
function mapPermissionRequest(request2) {
|
|
37080
|
+
const prompt = describeToolCall(request2.toolCall) ?? "The agent requested permission to continue.";
|
|
36738
37081
|
const optionIdByLabel = {};
|
|
36739
37082
|
const kindByLabel = {};
|
|
36740
37083
|
const labels = [];
|
|
36741
|
-
for (const opt of
|
|
37084
|
+
for (const opt of request2.options) {
|
|
36742
37085
|
const label = opt.name?.trim() || humanizeKind(opt.kind);
|
|
36743
37086
|
if (label in optionIdByLabel) continue;
|
|
36744
37087
|
optionIdByLabel[label] = opt.optionId;
|
|
@@ -36834,10 +37177,10 @@ function guardrailBlockNotice(decision, toolCall) {
|
|
|
36834
37177
|
return `\u26D4 Guardrail auto-blocked ${describeToolCallForNotice(toolCall)} \u2014 ${decision.reason} This session's "${decision.category}" guardrail is set to Deny (adjust it in the session's Guardrails settings if intended). This was an automatic block, not a rejection by you.`;
|
|
36835
37178
|
}
|
|
36836
37179
|
function createOnRequestPermission(deps) {
|
|
36837
|
-
return async (
|
|
37180
|
+
return async (request2) => {
|
|
36838
37181
|
let guardrailConfirm = false;
|
|
36839
37182
|
if (!deps.isLocal()) {
|
|
36840
|
-
const denied = internalPathPermissionOutcome(
|
|
37183
|
+
const denied = internalPathPermissionOutcome(request2);
|
|
36841
37184
|
if (denied) {
|
|
36842
37185
|
log.warn(
|
|
36843
37186
|
"acpRunner",
|
|
@@ -36845,17 +37188,17 @@ function createOnRequestPermission(deps) {
|
|
|
36845
37188
|
);
|
|
36846
37189
|
void deps.publisher.publishOutput({
|
|
36847
37190
|
type: "text",
|
|
36848
|
-
content: internalBlockNotice(
|
|
37191
|
+
content: internalBlockNotice(request2.toolCall),
|
|
36849
37192
|
done: true
|
|
36850
37193
|
});
|
|
36851
37194
|
return denied;
|
|
36852
37195
|
}
|
|
36853
|
-
const g = guardrailDecision(
|
|
37196
|
+
const g = guardrailDecision(request2, deps.getPolicy());
|
|
36854
37197
|
if (g?.kind === "deny") {
|
|
36855
37198
|
log.warn("acpRunner", `guardrail [${g.category}] \u2014 denying tool call`);
|
|
36856
37199
|
void deps.publisher.publishOutput({
|
|
36857
37200
|
type: "text",
|
|
36858
|
-
content: guardrailBlockNotice(g,
|
|
37201
|
+
content: guardrailBlockNotice(g, request2.toolCall),
|
|
36859
37202
|
done: true
|
|
36860
37203
|
});
|
|
36861
37204
|
return g.outcome;
|
|
@@ -36866,7 +37209,7 @@ function createOnRequestPermission(deps) {
|
|
|
36866
37209
|
}
|
|
36867
37210
|
}
|
|
36868
37211
|
if (deps.autoApprovePermissions && !guardrailConfirm) {
|
|
36869
|
-
const allow = pickAllowOption(
|
|
37212
|
+
const allow = pickAllowOption(request2.options);
|
|
36870
37213
|
if (allow) {
|
|
36871
37214
|
log.info(
|
|
36872
37215
|
"acpRunner",
|
|
@@ -36876,7 +37219,7 @@ function createOnRequestPermission(deps) {
|
|
|
36876
37219
|
}
|
|
36877
37220
|
log.warn("acpRunner", "AUTO mode \u2014 no allow option offered; falling back to interactive");
|
|
36878
37221
|
}
|
|
36879
|
-
const { event, optionIdByLabel } = mapPermissionRequest(
|
|
37222
|
+
const { event, optionIdByLabel } = mapPermissionRequest(request2);
|
|
36880
37223
|
await deps.publisher.publishAwaitingAnswer(event);
|
|
36881
37224
|
return deps.registerPermission({
|
|
36882
37225
|
questionId: event.questionId,
|
|
@@ -42787,8 +43130,8 @@ async function runBatonSession(opts) {
|
|
|
42787
43130
|
// instance the baton's own AcpDriver.start bracketing uses.
|
|
42788
43131
|
beginLoadReplay: () => streaming.beginLoadReplay(),
|
|
42789
43132
|
endLoadReplay: () => streaming.endLoadReplay(),
|
|
42790
|
-
onRequestPermission: async (
|
|
42791
|
-
const { event, optionIdByLabel } = mapPermissionRequest(
|
|
43133
|
+
onRequestPermission: async (request2) => {
|
|
43134
|
+
const { event, optionIdByLabel } = mapPermissionRequest(request2);
|
|
42792
43135
|
await publisher.publishAwaitingAnswer(event);
|
|
42793
43136
|
return streaming.registerPermission({
|
|
42794
43137
|
questionId: event.questionId,
|
|
@@ -45990,7 +46333,7 @@ function checkChokidar() {
|
|
|
45990
46333
|
}
|
|
45991
46334
|
async function doctor(args2 = []) {
|
|
45992
46335
|
const json = args2.includes("--json");
|
|
45993
|
-
const cliVersion = true ? "2.
|
|
46336
|
+
const cliVersion = true ? "2.68.0" : "0.0.0-dev";
|
|
45994
46337
|
const apiBase2 = resolveApiBaseUrl();
|
|
45995
46338
|
const diagnosticId = (0, import_node_crypto13.randomUUID)();
|
|
45996
46339
|
log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
|
|
@@ -46381,7 +46724,7 @@ async function mcpRun(args2) {
|
|
|
46381
46724
|
// src/commands/version.ts
|
|
46382
46725
|
var import_picocolors15 = __toESM(require("picocolors"));
|
|
46383
46726
|
function version2() {
|
|
46384
|
-
const v = true ? "2.
|
|
46727
|
+
const v = true ? "2.68.0" : "unknown";
|
|
46385
46728
|
console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
|
|
46386
46729
|
}
|
|
46387
46730
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeam-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.68.0",
|
|
4
4
|
"description": "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device — async. The terminal companion for CodeAgent Mobile.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"test:coverage": "vitest run --coverage",
|
|
23
23
|
"typecheck": "tsc --noEmit",
|
|
24
24
|
"postinstall": "node dist/postinstall.js || true",
|
|
25
|
-
"prepublishOnly": "npm run build"
|
|
25
|
+
"prepublishOnly": "npm run build",
|
|
26
|
+
"e2e": "playwright test"
|
|
26
27
|
},
|
|
27
28
|
"keywords": [
|
|
28
29
|
"claude",
|
|
@@ -90,11 +91,14 @@
|
|
|
90
91
|
},
|
|
91
92
|
"devDependencies": {
|
|
92
93
|
"@codeam/shared": "*",
|
|
94
|
+
"@playwright/test": "^1.62.1",
|
|
95
|
+
"@types/jsdom": "^30.0.0",
|
|
93
96
|
"@types/node": "^22.0.0",
|
|
94
97
|
"@types/qrcode-terminal": "^0.12.0",
|
|
95
98
|
"@types/which": "^3.0.4",
|
|
96
99
|
"@types/ws": "^8.5.0",
|
|
97
100
|
"@vitest/coverage-v8": "^4.1.7",
|
|
101
|
+
"jsdom": "^29.1.1",
|
|
98
102
|
"node-pty": "^1.1.0",
|
|
99
103
|
"tsup": "^8.0.0",
|
|
100
104
|
"typescript": "^7.0.2",
|