contactsheet 0.1.4 → 0.1.5
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/README.md +13 -0
- package/dist/canvas/app.js +149 -106
- package/dist/canvas/app.js.map +4 -4
- package/dist/canvas/style.css +31 -0
- package/dist/cli.js +61 -0
- package/dist/cli.js.map +4 -4
- package/package.json +1 -1
package/dist/canvas/style.css
CHANGED
|
@@ -1187,3 +1187,34 @@ body:not([data-sidebar="off"]) #cs-hud { left: calc(var(--cs-sb-w) + 16px); }
|
|
|
1187
1187
|
box-shadow: 0 0 0 3px var(--cs-accent), 0 0 0 6px var(--cs-accent-32);
|
|
1188
1188
|
z-index: 4;
|
|
1189
1189
|
}
|
|
1190
|
+
|
|
1191
|
+
/* ---------- dev server 僵死横幅(全局,盖在一切之上但不拦事件之外的区域) ---------- */
|
|
1192
|
+
.cs-health {
|
|
1193
|
+
position: fixed;
|
|
1194
|
+
top: 12px;
|
|
1195
|
+
left: 50%;
|
|
1196
|
+
transform: translateX(-50%);
|
|
1197
|
+
z-index: var(--cs-z-overlay);
|
|
1198
|
+
display: flex;
|
|
1199
|
+
align-items: baseline;
|
|
1200
|
+
gap: var(--cs-sp-4);
|
|
1201
|
+
max-width: min(720px, 86vw);
|
|
1202
|
+
padding: var(--cs-sp-4) var(--cs-sp-6);
|
|
1203
|
+
border: 1px solid var(--cs-danger);
|
|
1204
|
+
border-radius: var(--cs-r-3);
|
|
1205
|
+
background: color-mix(in srgb, var(--cs-danger) 12%, var(--cs-s1));
|
|
1206
|
+
color: var(--cs-fg);
|
|
1207
|
+
font: var(--cs-fs-sm)/1.5 var(--cs-ff-sans);
|
|
1208
|
+
box-shadow: var(--cs-e3);
|
|
1209
|
+
/* 入场:@starting-style 由 transition 接住,无 JS 时序(MOTION-013);进入用 ease-out(MOTION-006) */
|
|
1210
|
+
transition: opacity var(--cs-dur-3) var(--cs-ease-out), translate var(--cs-dur-3) var(--cs-ease-out);
|
|
1211
|
+
}
|
|
1212
|
+
@starting-style {
|
|
1213
|
+
.cs-health { opacity: 0; translate: 0 -8px; }
|
|
1214
|
+
}
|
|
1215
|
+
.cs-health b { color: var(--cs-danger); font-weight: var(--cs-fw-bold); white-space: nowrap; }
|
|
1216
|
+
.cs-health span { color: var(--cs-fg-2); }
|
|
1217
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1218
|
+
.cs-health { transition: opacity var(--cs-dur-3) var(--cs-ease-out); }
|
|
1219
|
+
@starting-style { .cs-health { translate: none; } }
|
|
1220
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -1147,6 +1147,56 @@ async function pushToSession(cfg, text2, pid) {
|
|
|
1147
1147
|
return { ok: true, pid: target.pid, name: target.name };
|
|
1148
1148
|
}
|
|
1149
1149
|
|
|
1150
|
+
// src/server/health.ts
|
|
1151
|
+
var INTERVAL_MS = Number(process.env.CS_HEALTH_INTERVAL_MS ?? 2e4);
|
|
1152
|
+
var TIMEOUT_MS = Number(process.env.CS_HEALTH_TIMEOUT_MS ?? 1e4);
|
|
1153
|
+
var FAILS_TO_TRIP = 2;
|
|
1154
|
+
var current = { ok: true };
|
|
1155
|
+
function currentHealth() {
|
|
1156
|
+
return current;
|
|
1157
|
+
}
|
|
1158
|
+
function startHealthMonitor(cfg, broadcast2) {
|
|
1159
|
+
let fails = 0;
|
|
1160
|
+
let everOk = false;
|
|
1161
|
+
let timer = null;
|
|
1162
|
+
const probe = async () => {
|
|
1163
|
+
let failDetail = null;
|
|
1164
|
+
try {
|
|
1165
|
+
const r = await fetch(`${cfg.target}/__cs/registry`, { signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
1166
|
+
if (r.ok || r.status === 404) {
|
|
1167
|
+
everOk = true;
|
|
1168
|
+
fails = 0;
|
|
1169
|
+
if (!current.ok) {
|
|
1170
|
+
current = { ok: true, lastOkAt: Date.now() };
|
|
1171
|
+
broadcast2({ type: "health", ok: true });
|
|
1172
|
+
} else {
|
|
1173
|
+
current = { ok: true, lastOkAt: Date.now() };
|
|
1174
|
+
}
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
failDetail = `SSR \u63A2\u6D4B\u8FD4\u56DE HTTP ${r.status}`;
|
|
1178
|
+
} catch (err) {
|
|
1179
|
+
const e = err;
|
|
1180
|
+
failDetail = e.name === "TimeoutError" ? `SSR \u63A2\u6D4B ${TIMEOUT_MS / 1e3}s \u65E0\u54CD\u5E94` : `\u8FDE\u4E0D\u4E0A(${e.cause?.code ?? "network"})`;
|
|
1181
|
+
}
|
|
1182
|
+
if (!everOk) return;
|
|
1183
|
+
fails++;
|
|
1184
|
+
if (fails >= FAILS_TO_TRIP && current.ok) {
|
|
1185
|
+
current = { ok: false, detail: failDetail ?? "\u672A\u77E5", lastOkAt: current.lastOkAt };
|
|
1186
|
+
broadcast2({ type: "health", ok: false, detail: current.detail });
|
|
1187
|
+
console.warn(`[contactsheet] \u26A0 \u76EE\u6807 ${cfg.target} \u7591\u4F3C\u50F5\u6B7B:${current.detail}(\u9759\u6001\u53EF\u80FD\u4ECD 200)\u3002\u753B\u5E03\u5DF2\u6302\u6A2A\u5E45\u3002`);
|
|
1188
|
+
}
|
|
1189
|
+
};
|
|
1190
|
+
timer = setInterval(() => void probe(), INTERVAL_MS);
|
|
1191
|
+
timer.unref();
|
|
1192
|
+
void probe();
|
|
1193
|
+
return {
|
|
1194
|
+
close() {
|
|
1195
|
+
if (timer) clearInterval(timer);
|
|
1196
|
+
}
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1150
1200
|
// src/server/sse.ts
|
|
1151
1201
|
var clients = /* @__PURE__ */ new Set();
|
|
1152
1202
|
var heartbeat = null;
|
|
@@ -1383,6 +1433,10 @@ async function handleApi(cfg, req, res, pathname) {
|
|
|
1383
1433
|
const rel = await saveRef(cfg, body.name || "ref.png", body.dataBase64);
|
|
1384
1434
|
return sendJson(res, 200, { path: rel });
|
|
1385
1435
|
}
|
|
1436
|
+
if (pathname === "/__cs/api/health") {
|
|
1437
|
+
if (method !== "GET") return sendText(res, 405, "method not allowed");
|
|
1438
|
+
return sendJson(res, 200, currentHealth());
|
|
1439
|
+
}
|
|
1386
1440
|
if (pathname === "/__cs/api/context") {
|
|
1387
1441
|
if (method !== "GET") return sendText(res, 405, "method not allowed");
|
|
1388
1442
|
try {
|
|
@@ -1559,6 +1613,11 @@ async function startServer(cfg) {
|
|
|
1559
1613
|
res.destroy();
|
|
1560
1614
|
}
|
|
1561
1615
|
});
|
|
1616
|
+
proxy.on("proxyReq", (proxyReq, _req, res) => {
|
|
1617
|
+
res.on("close", () => {
|
|
1618
|
+
if (!res.writableEnded) proxyReq.destroy();
|
|
1619
|
+
});
|
|
1620
|
+
});
|
|
1562
1621
|
proxy.on("proxyRes", () => proxyLog.alive());
|
|
1563
1622
|
proxy.on("open", () => proxyLog.alive());
|
|
1564
1623
|
let mcp = null;
|
|
@@ -1594,9 +1653,11 @@ async function startServer(cfg) {
|
|
|
1594
1653
|
warn(`\u76D1\u542C\u5728 ${host}:${port} \u2014\u2014 \u753B\u5E03\u65E0\u9274\u6743,\u540C\u7F51\u7EDC\u7684\u4EFB\u4F55\u4EBA\u90FD\u80FD\u8BFB\u4F60\u7684\u6279\u6CE8\u3001\u5E76\u5411\u4F60\u7684 Claude Code \u4F1A\u8BDD\u63A8\u9001\u6D88\u606F\u3002\u53EA\u5728\u53EF\u4FE1\u7F51\u7EDC\u8FD9\u4E48\u505A\u3002`)
|
|
1595
1654
|
);
|
|
1596
1655
|
}
|
|
1656
|
+
const health = startHealthMonitor(cfg, broadcast);
|
|
1597
1657
|
return {
|
|
1598
1658
|
port,
|
|
1599
1659
|
async close() {
|
|
1660
|
+
health.close();
|
|
1600
1661
|
closeAll();
|
|
1601
1662
|
proxyLog.close();
|
|
1602
1663
|
proxy.close();
|