summrise-agent 1.2.462 → 1.2.464
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/bin/summrise.js +305 -63
- package/package.json +2 -2
- package/summrise-agent.exe +0 -0
package/bin/summrise.js
CHANGED
|
@@ -70,10 +70,12 @@ exports.sha256File = sha256File;
|
|
|
70
70
|
exports.resolveComponent = resolveComponent;
|
|
71
71
|
exports.ensureElectron = ensureElectron;
|
|
72
72
|
exports.statusReport = statusReport;
|
|
73
|
+
exports.updateWouldNotMove = updateWouldNotMove;
|
|
73
74
|
exports.isBehind = isBehind;
|
|
74
75
|
exports.behindBy = behindBy;
|
|
75
76
|
exports.updateReceiptPs = updateReceiptPs;
|
|
76
77
|
exports.updateBusyPath = updateBusyPath;
|
|
78
|
+
exports.busyMarkerPs = busyMarkerPs;
|
|
77
79
|
exports.awaitReleaseMarker = awaitReleaseMarker;
|
|
78
80
|
exports.releaseMarkerVerdict = releaseMarkerVerdict;
|
|
79
81
|
exports.boxedVersions = boxedVersions;
|
|
@@ -353,8 +355,8 @@ function desktopStartPs(installQ) {
|
|
|
353
355
|
"if (Get-Process electron -ErrorAction SilentlyContinue) { Write-Output 'already-running'; exit 0 }",
|
|
354
356
|
"# DO NOT RE-REGISTER THE TASK TO START IT. Register-ScheduledTask needs the interactive",
|
|
355
357
|
"# user's principal as DOMAIN\\user, and a shell running as a service account has no such",
|
|
356
|
-
|
|
357
|
-
|
|
358
|
+
'# mapping -- the device answered "No mapping between account names and security IDs was',
|
|
359
|
+
'# done ... UserId" from a PTY running as systemprofile, while the task itself was Ready.',
|
|
358
360
|
"# The task already carries the right principal -- `summrise setup` creates it -- so starting",
|
|
359
361
|
"# it is all that is needed.",
|
|
360
362
|
"$t = Get-ScheduledTask -TaskName SummriseDesktop -ErrorAction SilentlyContinue",
|
|
@@ -421,7 +423,12 @@ function parseTargetArg(arg) {
|
|
|
421
423
|
const port = Number(m[2]);
|
|
422
424
|
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
423
425
|
return null;
|
|
424
|
-
return {
|
|
426
|
+
return {
|
|
427
|
+
host: m[1],
|
|
428
|
+
port,
|
|
429
|
+
path: m[3] || "",
|
|
430
|
+
id: `${m[1]}:${port}${m[3] || ""}`,
|
|
431
|
+
};
|
|
425
432
|
}
|
|
426
433
|
/**
|
|
427
434
|
* JSON that survives the command line.
|
|
@@ -443,16 +450,31 @@ function deviceApi(method, pathname, body) {
|
|
|
443
450
|
const dir = ETC_DIR;
|
|
444
451
|
const token = deviceToken(dir);
|
|
445
452
|
if (!token)
|
|
446
|
-
return {
|
|
453
|
+
return {
|
|
454
|
+
ok: false,
|
|
455
|
+
error: "no device token in " + path.join(dir, "config.yaml"),
|
|
456
|
+
};
|
|
447
457
|
const port = agentPort(dir);
|
|
448
|
-
const args = [
|
|
458
|
+
const args = [
|
|
459
|
+
"-sS",
|
|
460
|
+
"-m",
|
|
461
|
+
"15",
|
|
462
|
+
"-X",
|
|
463
|
+
method,
|
|
464
|
+
"-H",
|
|
465
|
+
"Authorization: Bearer " + token,
|
|
466
|
+
];
|
|
449
467
|
if (body !== undefined) {
|
|
450
468
|
args.push("-H", "content-type: application/json", "-d", asciiJson(body));
|
|
451
469
|
}
|
|
452
470
|
args.push(`http://127.0.0.1:${port}${pathname}`);
|
|
453
471
|
const r = (0, child_process_1.spawnSync)("curl", args, { encoding: "utf8", timeout: 20000 });
|
|
454
472
|
if (r.error || r.status !== 0) {
|
|
455
|
-
return {
|
|
473
|
+
return {
|
|
474
|
+
ok: false,
|
|
475
|
+
error: `device unreachable on 127.0.0.1:${port}` +
|
|
476
|
+
(r.error ? ` (${r.error.message})` : ""),
|
|
477
|
+
};
|
|
456
478
|
}
|
|
457
479
|
try {
|
|
458
480
|
return { ok: true, body: JSON.parse(String(r.stdout || "").trim()) };
|
|
@@ -461,21 +483,15 @@ function deviceApi(method, pathname, body) {
|
|
|
461
483
|
return { ok: false, error: "device sent something that is not JSON" };
|
|
462
484
|
}
|
|
463
485
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
//
|
|
474
|
-
// THE RENDER IS PURE (`reportText`) and the fetching is three lines of `deviceApi`, so
|
|
475
|
-
// the format is testable without a device. Two rules it keeps: a server that did not
|
|
476
|
-
// answer is printed as NOT READ, never as an invented value; and a target that is down
|
|
477
|
-
// brings its outage log with it, because "it is down" without "since when, and how
|
|
478
|
-
// often" is the half of the answer that starts an argument.
|
|
486
|
+
/**
|
|
487
|
+
* A duration in the shapes the rest of the panel uses: `45s`, `4m`, `1h 04m`, `2d 4h`.
|
|
488
|
+
*
|
|
489
|
+
* IT USED TO CARRY FOURTEEN LINES ABOUT `summrise report`, a verb pruned in round 25 — including "THE
|
|
490
|
+
* RENDER IS PURE (`reportText`)", naming a function that occurs nowhere else in this file. The doc
|
|
491
|
+
* described a command an operator cannot run, on a function whose actual job went unsaid. Two rules from
|
|
492
|
+
* that block are worth keeping, and this is not the place they belong: a server that did not answer is
|
|
493
|
+
* printed as NOT READ, never as an invented value, and a target that is down brings its outage log.
|
|
494
|
+
*/
|
|
479
495
|
function fmtDuration(ms) {
|
|
480
496
|
const s = Math.max(0, Math.floor(Number(ms || 0) / 1000));
|
|
481
497
|
if (s < 60)
|
|
@@ -483,7 +499,9 @@ function fmtDuration(ms) {
|
|
|
483
499
|
if (s < 3600)
|
|
484
500
|
return `${Math.floor(s / 60)}m`;
|
|
485
501
|
const h = Math.floor(s / 3600);
|
|
486
|
-
return h >= 24
|
|
502
|
+
return h >= 24
|
|
503
|
+
? `${Math.floor(h / 24)}d ${h % 24}h`
|
|
504
|
+
: `${h}h ${String(Math.floor((s % 3600) / 60)).padStart(2, "0")}m`;
|
|
487
505
|
}
|
|
488
506
|
function targetLine(t, nowMs, width = 30) {
|
|
489
507
|
const s = t.summary || {};
|
|
@@ -491,19 +509,27 @@ function targetLine(t, nowMs, width = 30) {
|
|
|
491
509
|
const state = up ? "up" : s.up_now === false ? "down" : "no readings";
|
|
492
510
|
const id = String(t.id || "");
|
|
493
511
|
const since = s.since_ms ? ` ${fmtDuration(nowMs - s.since_ms)}` : "";
|
|
494
|
-
const status = s.last_status === null || s.last_status === undefined
|
|
512
|
+
const status = s.last_status === null || s.last_status === undefined
|
|
513
|
+
? ""
|
|
514
|
+
: ` HTTP ${s.last_status}`;
|
|
495
515
|
// A content check that did not find its text is the case a status code cannot express, so it
|
|
496
516
|
// is printed as its own word next to the code.
|
|
497
|
-
const match = s.last_expect_ok === false
|
|
517
|
+
const match = s.last_expect_ok === false
|
|
518
|
+
? " no match"
|
|
519
|
+
: s.last_expect_ok === true
|
|
520
|
+
? " matches"
|
|
521
|
+
: "";
|
|
498
522
|
const lat = s.latency ? ` ${s.latency.avg}ms avg` : "";
|
|
499
523
|
const pct = s.up_pct === null || s.up_pct === undefined ? "" : ` ${s.up_pct}% up`;
|
|
500
|
-
const drops = s.drops
|
|
524
|
+
const drops = s.drops
|
|
525
|
+
? ` ${s.drops} ${s.drops === 1 ? "drop" : "drops"}`
|
|
526
|
+
: "";
|
|
501
527
|
// The operator's own words belong on the line they explain, not in a separate view.
|
|
502
528
|
const note = t.note && t.note.text ? ` — ${t.note.text}` : "";
|
|
503
529
|
return `${up ? "UP " : s.up_now === false ? "DOWN" : "? "} ${id.padEnd(width)} ${state}${since}${status}${match}${lat}${pct}${drops}${note}`;
|
|
504
530
|
}
|
|
505
531
|
// ── MACHINE-READABLE OUTPUT ────────────────────────────────────────────────
|
|
506
|
-
// `summrise monitor list --json`
|
|
532
|
+
// `summrise monitor list --json` prints the DEVICE'S OWN ANSWER,
|
|
507
533
|
// projected rather than re-derived: the summaries, the transitions and the samples are the ones
|
|
508
534
|
// `/api/monitors` returns, so a script and the panel (and the AI) can never disagree about whether
|
|
509
535
|
// a target is up. The CLI adds only what the device cannot know — which device answered, and when
|
|
@@ -528,13 +554,23 @@ function monitorsJson({ device, askedAtMs, payload, only }) {
|
|
|
528
554
|
// The device's numbers, verbatim. `up: null` means "not read yet" and is NOT false.
|
|
529
555
|
up: t.summary && t.summary.up_now !== undefined ? t.summary.up_now : null,
|
|
530
556
|
up_pct: t.summary && t.summary.up_pct !== undefined ? t.summary.up_pct : null,
|
|
531
|
-
since_ms: t.summary && t.summary.since_ms !== undefined
|
|
557
|
+
since_ms: t.summary && t.summary.since_ms !== undefined
|
|
558
|
+
? t.summary.since_ms
|
|
559
|
+
: null,
|
|
532
560
|
drops: t.summary && t.summary.drops !== undefined ? t.summary.drops : null,
|
|
533
561
|
latency_ms: t.summary && t.summary.latency ? t.summary.latency.avg : null,
|
|
534
|
-
last_status: t.summary && t.summary.last_status !== undefined
|
|
535
|
-
|
|
562
|
+
last_status: t.summary && t.summary.last_status !== undefined
|
|
563
|
+
? t.summary.last_status
|
|
564
|
+
: null,
|
|
565
|
+
last_expect_ok: t.summary && t.summary.last_expect_ok !== undefined
|
|
566
|
+
? t.summary.last_expect_ok
|
|
567
|
+
: null,
|
|
536
568
|
probes: t.summary && t.summary.probes !== undefined ? t.summary.probes : null,
|
|
537
|
-
transitions: (t.transitions || []).map((x) => ({
|
|
569
|
+
transitions: (t.transitions || []).map((x) => ({
|
|
570
|
+
at_ms: x.at_ms,
|
|
571
|
+
up: x.up,
|
|
572
|
+
lasted_ms: x.lasted_ms,
|
|
573
|
+
})),
|
|
538
574
|
}));
|
|
539
575
|
return {
|
|
540
576
|
device: device || "",
|
|
@@ -919,6 +955,13 @@ function sha256File(p) {
|
|
|
919
955
|
*
|
|
920
956
|
* `null` when neither source works: every component here is optional to the
|
|
921
957
|
* AGENT, and a failed fetch must not fail the install.
|
|
958
|
+
*
|
|
959
|
+
* THE FIRST ARM IS A SEAM, NOT A LIVE PATH — measured, because a reader deserves to know which: no
|
|
960
|
+
* component is in `package.json`'s `files[]` or in `required-in-tgz.txt`, and `tar tzf` on the published
|
|
961
|
+
* tarball confirms none of the three is there. So today the release host is the ONLY source, and the
|
|
962
|
+
* `existsSync` above exists so that a future release which DOES box one gets it from the package without
|
|
963
|
+
* touching this function. The test beside it pins the fact, so boxing one is a deliberate act that also
|
|
964
|
+
* updates this sentence.
|
|
922
965
|
*/
|
|
923
966
|
function resolveComponent(name, pkgPath) {
|
|
924
967
|
if (fs.existsSync(pkgPath))
|
|
@@ -1069,9 +1112,22 @@ function statusReport(f) {
|
|
|
1069
1112
|
* sentence's truthiness — where "0 releases" and "-1 releases" are both TRUTHY, so it would have refused every update,
|
|
1070
1113
|
* including the correct one. Caught by asking the artefact before shipping it. */
|
|
1071
1114
|
function versionTriple(v) {
|
|
1072
|
-
const t = String(v || "")
|
|
1115
|
+
const t = String(v || "")
|
|
1116
|
+
.split(".")
|
|
1117
|
+
.map(Number);
|
|
1073
1118
|
return t.length === 3 && t.every((n) => Number.isFinite(n)) ? t : null;
|
|
1074
1119
|
}
|
|
1120
|
+
/** WOULD THIS UPDATE MOVE THE DEVICE AT ALL? The parity fact, and it needs NO NETWORK: the CLI stamps
|
|
1121
|
+
* `<install>/.summrise-release` with ITS OWN version, and the device's `agent_update` reads that file as
|
|
1122
|
+
* the local version — so when the device is already on the version this CLI carries, the stamp equals
|
|
1123
|
+
* what is there, the swap installs the same build, and nothing moves. That is round 201's measured
|
|
1124
|
+
* defect ("update requested 1.2.438 -> 1.2.438", "copy ok=True", release unchanged), which is why the
|
|
1125
|
+
* guard below exists at all. THE GUARD THAT EXISTS CANNOT SEE IT: it compares the CLI against the
|
|
1126
|
+
* RELEASE CHANNEL, and when the CDN is unreadable `latest` is empty and the whole check is skipped, so a
|
|
1127
|
+
* network blip re-opens the defect. This one compares two facts already on the machine. */
|
|
1128
|
+
function updateWouldNotMove(fromVersion, selfVersion) {
|
|
1129
|
+
return Boolean(fromVersion) && fromVersion === selfVersion;
|
|
1130
|
+
}
|
|
1075
1131
|
/** Is `latest` ahead of `device` on the SAME release line? */
|
|
1076
1132
|
function isBehind(device, latest) {
|
|
1077
1133
|
const a = versionTriple(device);
|
|
@@ -1131,6 +1187,21 @@ function updateReceiptPs(dataDirQ, fromVersion, toVersion) {
|
|
|
1131
1187
|
function updateBusyPath() {
|
|
1132
1188
|
return path.join(process.env.ProgramData || "C:\\ProgramData", "SummriseAgent", "update-busy");
|
|
1133
1189
|
}
|
|
1190
|
+
/**
|
|
1191
|
+
* The SAME marker as the PowerShell text the swap script embeds, DERIVED from `updateBusyPath()` rather
|
|
1192
|
+
* than written out by hand — which is what this file did, three times, inside the generated script. The
|
|
1193
|
+
* agent fixed exactly this on its own side and says why (tools.rs, above BUSY_MARKER_REL): "It used to be
|
|
1194
|
+
* spelled out TWICE: the Rust acquirer built <ProgramData>\SummriseAgent\update-busy from PathBuf joins
|
|
1195
|
+
* while the generated PowerShell swap script carried the same location as two hand-written string
|
|
1196
|
+
* literals. A drift between the two is invisible until an update actually runs — and then the swap
|
|
1197
|
+
* releases a file the agent never created, the marker survives, and every later update is refused for up
|
|
1198
|
+
* to an hour." The CLI's own doc above claims ONE owner for this path; this is what makes that true.
|
|
1199
|
+
*/
|
|
1200
|
+
function busyMarkerPs() {
|
|
1201
|
+
const root = process.env.ProgramData || "C:\\ProgramData";
|
|
1202
|
+
const rel = path.relative(root, updateBusyPath()).split(path.sep).join("\\");
|
|
1203
|
+
return `(Join-Path $env:ProgramData '${rel}')`;
|
|
1204
|
+
}
|
|
1134
1205
|
/**
|
|
1135
1206
|
* Poll `etc\.summrise-release` until it shows `want`, or the budget expires.
|
|
1136
1207
|
*
|
|
@@ -1167,11 +1238,17 @@ async function awaitReleaseMarker(o) {
|
|
|
1167
1238
|
* misreporting its own version and refusing the update that would fix it.
|
|
1168
1239
|
*/
|
|
1169
1240
|
function releaseMarkerVerdict(c) {
|
|
1241
|
+
// WHOSE FAILURE IS THIS? The message is printed by TWO callers — `rollback`, which staged a release in
|
|
1242
|
+
// order to PIN it, and `update`, which staged one to INSTALL it — and it said "rollback:" with
|
|
1243
|
+
// "re-run `summrise rollback <want>`" for both. On the update path that advice is worse than useless:
|
|
1244
|
+
// it names the version that just failed to install, so following it asks the device to pin a release
|
|
1245
|
+
// it is not running. The verb decides the sentence now.
|
|
1246
|
+
const verb = c.verb ?? "rollback";
|
|
1170
1247
|
if (c.ok) {
|
|
1171
1248
|
return {
|
|
1172
1249
|
writePin: true,
|
|
1173
1250
|
exitCode: 0,
|
|
1174
|
-
message:
|
|
1251
|
+
message: `${verb}: pinned to ${c.want} -- auto-upgrade refused until 'summrise rollback --clear' or a forced agent_update`,
|
|
1175
1252
|
};
|
|
1176
1253
|
}
|
|
1177
1254
|
return {
|
|
@@ -1181,12 +1258,17 @@ function releaseMarkerVerdict(c) {
|
|
|
1181
1258
|
// failed, or it is still running (it kills and restarts the agent), or the marker
|
|
1182
1259
|
// could not be read at all — and `saw === null` is the weakest of the three. The old
|
|
1183
1260
|
// wording asserted "the swap did NOT take" for all of them.
|
|
1184
|
-
message:
|
|
1261
|
+
message: `${verb}: no release marker showing ${c.want} within ${Number.isFinite(c.waitedMs)
|
|
1185
1262
|
? Math.round(c.waitedMs / 1000) + "s"
|
|
1186
1263
|
: "the read-back window"} ` +
|
|
1187
1264
|
`(last read: ${c.saw ?? "empty or unreadable"}). ` +
|
|
1188
1265
|
`NOT pinned (a pin would claim a version this device may not be running) and no release ` +
|
|
1189
|
-
`marker written.
|
|
1266
|
+
`marker written. ` +
|
|
1267
|
+
(verb === "update"
|
|
1268
|
+
? `Check the update log and \`summrise status\`, then re-run \`summrise update\`. If it fails ` +
|
|
1269
|
+
`the same way, \`summrise rollback ${c.from ?? "<the version it was on>"}\` pins the release ` +
|
|
1270
|
+
`this device is ACTUALLY running — which is not the one that failed to install.`
|
|
1271
|
+
: `Check the update log and \`summrise status\`, then re-run 'summrise rollback ${c.want}'.`),
|
|
1190
1272
|
};
|
|
1191
1273
|
}
|
|
1192
1274
|
function boxedVersions(installDir, pkgDir) {
|
|
@@ -1367,6 +1449,28 @@ function svc(action) {
|
|
|
1367
1449
|
// silent either way. `autostart` already checks; this is the same pattern.
|
|
1368
1450
|
return sh(`schtasks /${action} /TN ${TASK}`, { stdio: "inherit" });
|
|
1369
1451
|
}
|
|
1452
|
+
/**
|
|
1453
|
+
* The scheduled task's State (`Running`, `Ready`, `Disabled`, …) or **null when it could not be read**.
|
|
1454
|
+
*
|
|
1455
|
+
* THREE STATES, NOT ONE. `autostart` states the rule and names the shape — "a failed READ reported as
|
|
1456
|
+
* evidence of ABSENCE" — which it calls the THIRD instance, after `statusReport` and `rollback status`.
|
|
1457
|
+
* Here `null` means "I could not ask", and callers report that as itself instead of as a verdict.
|
|
1458
|
+
*
|
|
1459
|
+
* `LOCALE` is why this asks `Get-ScheduledTask` rather than parsing `schtasks /Query`: the latter's headers
|
|
1460
|
+
* are localized, which is the reason `autostart` uses this call too.
|
|
1461
|
+
*/
|
|
1462
|
+
function taskState(name) {
|
|
1463
|
+
const r = (0, child_process_1.spawnSync)("powershell", [
|
|
1464
|
+
"-NoProfile",
|
|
1465
|
+
"-Command",
|
|
1466
|
+
`(Get-ScheduledTask -TaskName '${name}' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty State)`,
|
|
1467
|
+
], { encoding: "utf8" });
|
|
1468
|
+
const err = r && r.error;
|
|
1469
|
+
if (!r || err || r.status !== 0)
|
|
1470
|
+
return null; // spawn failed, or the read itself failed
|
|
1471
|
+
const s = String(r.stdout || "").trim();
|
|
1472
|
+
return s === "" ? null : s; // empty = the task is not there, which is also "no answer to Running?"
|
|
1473
|
+
}
|
|
1370
1474
|
// Shared tunnel bootstrap: login (token or interactive) → create tunnel →
|
|
1371
1475
|
// DNS route → write tunnel.yml. Used by `summrise setup --tunnel` and
|
|
1372
1476
|
// `summrise tunnel install`.
|
|
@@ -1375,7 +1479,12 @@ function initTunnel(hostname, regKey) {
|
|
|
1375
1479
|
const cfg = path.join(ETC_DIR, "tunnel.yml");
|
|
1376
1480
|
if (!fs.existsSync(cf)) {
|
|
1377
1481
|
console.error("tunnel: cloudflared.exe not staged at", cf);
|
|
1378
|
-
|
|
1482
|
+
// NOT "REINSTALL THE PACKAGE". The npm package carries NO boxed components BY DESIGN (that is what
|
|
1483
|
+
// keeps it ~6.7 MB), so a reinstall cannot stage this and the advice sent the operator in a circle.
|
|
1484
|
+
// `setup` is what stages it: resolveComponent fetches it from the RELEASE HOST and the agent's own
|
|
1485
|
+
// sha256 pin checks it again on the path it uses. Its doc records what this costs when nobody does —
|
|
1486
|
+
// "a device with no tunnel is INVISIBLE TO THE CONSOLE while looking perfectly healthy from inside."
|
|
1487
|
+
console.error(" run 'summrise setup' to stage it -- the component comes from the release host, not from the npm package.");
|
|
1379
1488
|
process.exit(1);
|
|
1380
1489
|
}
|
|
1381
1490
|
const host = hostname || "d1.agent.saisi.online";
|
|
@@ -1613,7 +1722,13 @@ const commands = {
|
|
|
1613
1722
|
// `regOk` is collected here and summarised at the end of setup: best-effort, but the
|
|
1614
1723
|
// operator should be told once, with the consequence, rather than not at all.
|
|
1615
1724
|
regOk.push(regWrite("InstallDir", DIR));
|
|
1616
|
-
|
|
1725
|
+
// ...AND ECHO THE RESOLVED ONE, NOT THE DEFAULT. `resolveDataDir()` is registry-first and
|
|
1726
|
+
// `DATA_DIR` is its answer, so a device whose data dir was remapped has that path here. This used to
|
|
1727
|
+
// WRITE THE LITERAL DEFAULT back to the registry — while the tree below was created at `DATA_DIR` —
|
|
1728
|
+
// so the new tree landed at the remapped path and the registry then named the default, which is the
|
|
1729
|
+
// path the AGENT reads. The comment two lines up says "DataDir defaults to %ProgramData%\Summrise",
|
|
1730
|
+
// i.e. exactly what the code did not do: a default applies when nothing is set, not over a remap.
|
|
1731
|
+
regOk.push(regWrite("DataDir", DATA_DIR));
|
|
1617
1732
|
// Pre-create the data dir tree (sessions/memory/logs — C1 separation).
|
|
1618
1733
|
const DATA = DATA_DIR;
|
|
1619
1734
|
for (const sub of ["sessions", "memory", "logs"]) {
|
|
@@ -1760,7 +1875,10 @@ const commands = {
|
|
|
1760
1875
|
// shims rather than hand-written ones; when npm cannot, say the command.
|
|
1761
1876
|
{
|
|
1762
1877
|
const selfVer = String(require("../package.json").version || "");
|
|
1763
|
-
const pfx = (0, child_process_1.spawnSync)("npm", ["prefix", "-g"], {
|
|
1878
|
+
const pfx = (0, child_process_1.spawnSync)("npm", ["prefix", "-g"], {
|
|
1879
|
+
encoding: "utf8",
|
|
1880
|
+
shell: true,
|
|
1881
|
+
});
|
|
1764
1882
|
const pre = pfx.status === 0 ? String(pfx.stdout || "").trim() : "";
|
|
1765
1883
|
if (selfVer && pre) {
|
|
1766
1884
|
const inst = (0, child_process_1.spawnSync)("npm", [
|
|
@@ -1793,12 +1911,25 @@ const commands = {
|
|
|
1793
1911
|
try {
|
|
1794
1912
|
const reg = path.join(SCRIPTS_DIR, "register-desktop-task.ps1");
|
|
1795
1913
|
fs.writeFileSync(reg, desktopTaskPs(DIR).join("\r\n") + "\r\n");
|
|
1796
|
-
|
|
1797
|
-
//
|
|
1798
|
-
//
|
|
1799
|
-
//
|
|
1800
|
-
|
|
1801
|
-
|
|
1914
|
+
// THE STATUS WAS DISCARDED AND THE SUCCESS LINE WAS UNCONDITIONAL, which is the defect the AGENT
|
|
1915
|
+
// task's check ten lines below names in its own comment: "audit #7: used to claim success
|
|
1916
|
+
// regardless". Worse, the `catch` below could not fire for this — `sh()` RETURNS, it does not
|
|
1917
|
+
// throw — so the only thing it ever caught was `writeFileSync`.
|
|
1918
|
+
//
|
|
1919
|
+
// ONE CALL, and its status is READ. DOUBLE quotes in the command: a cmd-layer argument, because the
|
|
1920
|
+
// single-quoted version this shipped with reached PowerShell with the quotes included and died on
|
|
1921
|
+
// the device with "unsupported path format" for a path that was perfectly valid.
|
|
1922
|
+
//
|
|
1923
|
+
// A WARNING, NOT FATAL, unlike the agent task: headless installs have no SummriseDesktop, and the
|
|
1924
|
+
// registration script itself exits quietly when electron is already alive.
|
|
1925
|
+
const reg302 = sh(`powershell -NoProfile -ExecutionPolicy Bypass -File "${reg}"`, { stdio: "pipe" });
|
|
1926
|
+
if (reg302 && reg302.status === 0) {
|
|
1927
|
+
console.log("setup: SummriseDesktop registered (logon + a 5-minute watchdog) and started");
|
|
1928
|
+
}
|
|
1929
|
+
else {
|
|
1930
|
+
console.log(`setup: WARNING -- registering SummriseDesktop failed (status ${reg302 ? reg302.status : "spawn error"}); ` +
|
|
1931
|
+
`run: powershell -File "${SCRIPTS_DIR}\\register-desktop-task.ps1"`);
|
|
1932
|
+
}
|
|
1802
1933
|
}
|
|
1803
1934
|
catch {
|
|
1804
1935
|
console.log(`setup: WARNING -- could not register SummriseDesktop; run: powershell -File "${SCRIPTS_DIR}\\register-desktop-task.ps1"`);
|
|
@@ -1890,10 +2021,10 @@ const commands = {
|
|
|
1890
2021
|
console.log("setup: no tunnel configured (local mode). Enable later with `summrise tunnel install <hostname>`.");
|
|
1891
2022
|
}
|
|
1892
2023
|
},
|
|
1893
|
-
// ── monitor
|
|
2024
|
+
// ── monitor ────────────────────────────────────────────────────────────
|
|
1894
2025
|
// The device's reachability instrument, in the terminal the operator already
|
|
1895
2026
|
// works in. The panel draws the same numbers; this runs where the ssh session
|
|
1896
|
-
// is, needs no browser, and
|
|
2027
|
+
// is, needs no browser, and runs its live view until Ctrl+C.
|
|
1897
2028
|
// async: `wait` blocks on probes (the dispatcher already awaits every command).
|
|
1898
2029
|
async monitor(args) {
|
|
1899
2030
|
const sub = String(args[0] || "list").toLowerCase();
|
|
@@ -1903,7 +2034,7 @@ const commands = {
|
|
|
1903
2034
|
const expect = expectAt >= 0 ? String(args[expectAt + 1] || "") : "";
|
|
1904
2035
|
const positional = args.filter((a, i) => i > 0 && a !== "--expect" && i !== expectAt + 1);
|
|
1905
2036
|
if (expectAt >= 0 && !expect) {
|
|
1906
|
-
console.error(
|
|
2037
|
+
console.error('usage: summrise monitor add <host:port[/path]> --expect "<text>"');
|
|
1907
2038
|
process.exit(1);
|
|
1908
2039
|
}
|
|
1909
2040
|
if (sub === "add" || sub === "rm" || sub === "remove") {
|
|
@@ -1913,7 +2044,12 @@ const commands = {
|
|
|
1913
2044
|
process.exit(1);
|
|
1914
2045
|
}
|
|
1915
2046
|
if (sub === "add") {
|
|
1916
|
-
const r = deviceApi("POST", "/api/monitors/add", {
|
|
2047
|
+
const r = deviceApi("POST", "/api/monitors/add", {
|
|
2048
|
+
host: t.host,
|
|
2049
|
+
port: t.port,
|
|
2050
|
+
path: t.path,
|
|
2051
|
+
expect,
|
|
2052
|
+
});
|
|
1917
2053
|
if (!r.ok) {
|
|
1918
2054
|
console.error(`monitor add: ${r.error}`);
|
|
1919
2055
|
process.exit(1);
|
|
@@ -1934,7 +2070,9 @@ const commands = {
|
|
|
1934
2070
|
console.error(`monitor rm: ${r.error}`);
|
|
1935
2071
|
process.exit(1);
|
|
1936
2072
|
}
|
|
1937
|
-
console.log(r.body && r.body.removed
|
|
2073
|
+
console.log(r.body && r.body.removed
|
|
2074
|
+
? `stopped watching ${t.id}`
|
|
2075
|
+
: `monitor rm: ${t.id} was not being watched`);
|
|
1938
2076
|
return;
|
|
1939
2077
|
}
|
|
1940
2078
|
if (sub === "probe") {
|
|
@@ -1964,7 +2102,10 @@ const commands = {
|
|
|
1964
2102
|
const probe = payload.result && payload.result.probe;
|
|
1965
2103
|
// The device sends the CRITERION with the answer (`expect`), so "no match" can name the
|
|
1966
2104
|
// text it looked for — an unreadable verdict is a verdict nobody can act on.
|
|
1967
|
-
console.log(probeLine({
|
|
2105
|
+
console.log(probeLine({
|
|
2106
|
+
id: t.id,
|
|
2107
|
+
expect: (payload.result && payload.result.expect) || null,
|
|
2108
|
+
}, probe, Date.now()));
|
|
1968
2109
|
return;
|
|
1969
2110
|
}
|
|
1970
2111
|
if (sub !== "list") {
|
|
@@ -1985,7 +2126,12 @@ const commands = {
|
|
|
1985
2126
|
// encoding boundary too. PowerShell decodes a child's output with the console code page
|
|
1986
2127
|
// (CP936 on d1), so raw UTF-8 through a pipe came back as mojibake while the same text
|
|
1987
2128
|
// printed directly read fine — the second face of this bug.
|
|
1988
|
-
console.log(asciiJson(monitorsJson({
|
|
2129
|
+
console.log(asciiJson(monitorsJson({
|
|
2130
|
+
device: os.hostname(),
|
|
2131
|
+
askedAtMs: Date.now(),
|
|
2132
|
+
payload: r.body,
|
|
2133
|
+
only: null,
|
|
2134
|
+
}), 2));
|
|
1989
2135
|
return;
|
|
1990
2136
|
}
|
|
1991
2137
|
const targets = (r.body && r.body.targets) || [];
|
|
@@ -1999,7 +2145,7 @@ const commands = {
|
|
|
1999
2145
|
for (const t of targets)
|
|
2000
2146
|
console.log(targetLine(t, now));
|
|
2001
2147
|
},
|
|
2002
|
-
// Live view: redraw in place every few seconds until Ctrl+C. `summrise
|
|
2148
|
+
// Live view: redraw in place every few seconds until Ctrl+C. `summrise monitor
|
|
2003
2149
|
// <host:port[/path]>` narrows it to one target and adds its outage log.
|
|
2004
2150
|
// `summrise desktop` = put the window back, or say it is already there.
|
|
2005
2151
|
//
|
|
@@ -2045,6 +2191,17 @@ const commands = {
|
|
|
2045
2191
|
process.exit(1);
|
|
2046
2192
|
},
|
|
2047
2193
|
status() {
|
|
2194
|
+
// THE EXIT CODE IS 0 EVEN WHEN THE REPORT SAYS "UNKNOWN", AND THAT IS THE CONVENTION.
|
|
2195
|
+
//
|
|
2196
|
+
// `status` is asked for a REPORT; it produces one, and the verdict lives in the text. `tunnel`
|
|
2197
|
+
// exits 1 on the same unreadable process list (`:3655-3662`) because the thing IT was asked for —
|
|
2198
|
+
// a running tunnel — did not happen. The two are not inconsistent; they answer different
|
|
2199
|
+
// questions, and the difference is what the caller asked for rather than how bad the news is.
|
|
2200
|
+
//
|
|
2201
|
+
// A script that needs a VERDICT must not read this exit code. `monitor list --json` states its own
|
|
2202
|
+
// contract at `:2424-2426`: "A device that could not be reached is a NON-ZERO EXIT with the reason
|
|
2203
|
+
// on stderr, never a JSON body pretending everything is fine."
|
|
2204
|
+
//
|
|
2048
2205
|
// NOT via shell: `shell: true` concatenates argv into one cmd.exe string,
|
|
2049
2206
|
// so the unquoted filter "IMAGENAME eq …" was split at its spaces, tasklist
|
|
2050
2207
|
// rejected it, and this ALWAYS printed STOPPED even with the agent running.
|
|
@@ -2109,23 +2266,59 @@ const commands = {
|
|
|
2109
2266
|
}
|
|
2110
2267
|
},
|
|
2111
2268
|
start() {
|
|
2112
|
-
svc
|
|
2269
|
+
// CHECK IT, LIKE `stop` DOES. The comment on `svc` above says the return value exists because
|
|
2270
|
+
// discarding it made `start`/`restart` "silent either way" — and two of the four call sites still
|
|
2271
|
+
// discarded it, including this one, which is step 2 of every documented journey. An operator whose
|
|
2272
|
+
// agent never came up got exit 0 and no sentence at all.
|
|
2273
|
+
const r = svc("Run");
|
|
2274
|
+
if (r && r.status !== 0) {
|
|
2275
|
+
console.error(`summrise start: schtasks /Run failed (status ${r.status}) -- the agent is NOT running`);
|
|
2276
|
+
process.exit(1);
|
|
2277
|
+
}
|
|
2113
2278
|
},
|
|
2114
2279
|
stop() {
|
|
2115
2280
|
// Say WHY before the process goes: see markDeliberateStop.
|
|
2116
2281
|
markDeliberateStop();
|
|
2117
2282
|
const r = svc("End");
|
|
2118
2283
|
if (r && r.status !== 0) {
|
|
2119
|
-
|
|
2120
|
-
|
|
2284
|
+
// A NON-ZERO `/End` IS NOT EVIDENCE THE AGENT IS STILL RUNNING, and the mirror of this mistake is
|
|
2285
|
+
// already documented twice in this file. `restart` says it about the identical condition: "a task
|
|
2286
|
+
// that was not running has nothing to end, so a non-zero /End is the ordinary case". `svc`'s own
|
|
2287
|
+
// comment records the ORIGINAL bug, which was the opposite — "`stop` printed 'stopped' and exited 0
|
|
2288
|
+
// for a missing task or an access-denied". So the exit code answers neither question.
|
|
2289
|
+
//
|
|
2290
|
+
// ASK INSTEAD. `autostart` reads `Get-ScheduledTask ... -ExpandProperty State` and its comment names
|
|
2291
|
+
// this exact shape — "a failed READ reported as evidence of ABSENCE" — which it calls the THIRD
|
|
2292
|
+
// instance. This is the fourth, from the other side: an ordinary read taken as evidence of PRESENCE,
|
|
2293
|
+
// with "the agent may still be running" printed for a task that had already stopped.
|
|
2294
|
+
const st = taskState(TASK);
|
|
2295
|
+
if (st === null) {
|
|
2296
|
+
// Could not read it — say that, rather than guessing either way (the three-state rule).
|
|
2297
|
+
console.error(`summrise stop: schtasks /End failed (status ${r.status}) and the task's state could not be read`);
|
|
2298
|
+
process.exit(1);
|
|
2299
|
+
}
|
|
2300
|
+
if (st === "Running") {
|
|
2301
|
+
console.error(`summrise stop: schtasks /End failed (status ${r.status}) and the task is still Running`);
|
|
2302
|
+
process.exit(1);
|
|
2303
|
+
}
|
|
2304
|
+
console.error(`summrise stop: schtasks /End returned ${r.status}, but the task is not Running -- nothing to stop`);
|
|
2121
2305
|
}
|
|
2122
2306
|
console.log("stopped -- revives via 'summrise start' or the 5-min watchdog ('summrise autostart off' opts out of autostart)");
|
|
2123
2307
|
},
|
|
2124
2308
|
restart() {
|
|
2125
2309
|
markDeliberateStop();
|
|
2126
|
-
|
|
2310
|
+
// THE STOP IS REPORTED BUT NOT FATAL: a task that was not running has nothing to end, so a non-zero
|
|
2311
|
+
// `/End` is the ordinary case for "restart a stopped agent", and the operator asked for the START.
|
|
2312
|
+
const end = svc("End");
|
|
2313
|
+
if (end && end.status !== 0) {
|
|
2314
|
+
console.error(`summrise restart: schtasks /End failed (status ${end.status}) -- the old process may still be running`);
|
|
2315
|
+
}
|
|
2127
2316
|
sh("timeout /t 2 >nul");
|
|
2128
|
-
svc("Run");
|
|
2317
|
+
const run = svc("Run");
|
|
2318
|
+
if (run && run.status !== 0) {
|
|
2319
|
+
console.error(`summrise restart: schtasks /Run failed (status ${run.status}) -- the agent is NOT running`);
|
|
2320
|
+
process.exit(1);
|
|
2321
|
+
}
|
|
2129
2322
|
},
|
|
2130
2323
|
// Boot switch: `summrise autostart on|off|status` (default status). Flips the
|
|
2131
2324
|
// ENABLED flag on BOTH boot tasks (service + desktop shell) — this is the
|
|
@@ -2225,6 +2418,28 @@ const commands = {
|
|
|
2225
2418
|
"\n summrise update");
|
|
2226
2419
|
process.exit(1);
|
|
2227
2420
|
}
|
|
2421
|
+
// AND THE SAME REFUSAL WITHOUT THE NETWORK. The guard above cannot fire when the CDN is
|
|
2422
|
+
// unreadable, and the defect it exists for is exactly what happens then: an update that stamps the
|
|
2423
|
+
// install with a version it already has, swaps in the same build, and reports success while
|
|
2424
|
+
// `status` keeps saying the device is behind.
|
|
2425
|
+
let deviceNow = "";
|
|
2426
|
+
try {
|
|
2427
|
+
deviceNow = fs
|
|
2428
|
+
.readFileSync(path.join(ETC_DIR, ".summrise-release"), "utf8")
|
|
2429
|
+
.trim();
|
|
2430
|
+
}
|
|
2431
|
+
catch {
|
|
2432
|
+
// No marker is not a no-op; the update below writes one.
|
|
2433
|
+
}
|
|
2434
|
+
if (updateWouldNotMove(deviceNow, selfVersion)) {
|
|
2435
|
+
console.error(`update: this CLI is ${selfVersion} and the device is ALREADY on ${deviceNow}.` +
|
|
2436
|
+
"\n Updating from here would stamp the device with the version it already has and change nothing," +
|
|
2437
|
+
"\n because the device reads <install>/.summrise-release (written by this package) as its version." +
|
|
2438
|
+
"\n If the device is meant to be newer, this CLI is too old to deliver it: install the new one first." +
|
|
2439
|
+
"\n npm i -g summrise-agent" +
|
|
2440
|
+
"\n summrise update");
|
|
2441
|
+
process.exit(1);
|
|
2442
|
+
}
|
|
2228
2443
|
// npm audit #10: no mutual exclusion — two updates (or setup racing a
|
|
2229
2444
|
// swap) interleave Copy-Item on *.new, leaving a half-written exe "ok".
|
|
2230
2445
|
// setup REMOVES the marker; update now CREATES it (refuse if <10 min
|
|
@@ -2410,7 +2625,7 @@ const commands = {
|
|
|
2410
2625
|
// BEFORE touching anything (fail-closed — a config-path argument boots
|
|
2411
2626
|
// old AND new agents alike, so aborting here leaves the old version
|
|
2412
2627
|
// running untouched). Without this the moved config strands the boot.
|
|
2413
|
-
`try { ${bootTaskPs(`${q}\\summrise-agent.exe`, `${q}\\etc\\config.yaml`, false).join("; ")} } catch { "[$(Get-Date -Format o)] task repoint FAILED: $($_.Exception.Message)" | ${log}; try { Remove-Item -Force (
|
|
2628
|
+
`try { ${bootTaskPs(`${q}\\summrise-agent.exe`, `${q}\\etc\\config.yaml`, false).join("; ")} } catch { "[$(Get-Date -Format o)] task repoint FAILED: $($_.Exception.Message)" | ${log}; try { Remove-Item -Force (${busyMarkerPs()}) } catch {}; exit 1 }`,
|
|
2414
2629
|
`"[$(Get-Date -Format o)] task repointed at etc\\config.yaml" | ${log}`,
|
|
2415
2630
|
// Layout-v2 migration (ADR 0008): move pre-v2 root paths into their
|
|
2416
2631
|
// v2 homes. Best-effort per item; the gate below is fail-closed.
|
|
@@ -2418,7 +2633,7 @@ const commands = {
|
|
|
2418
2633
|
// Fail-closed gate: the new agent reads ONLY the v2 homes. A missing
|
|
2419
2634
|
// config/hostname here means migration failed — do NOT swap (the old
|
|
2420
2635
|
// exe keeps running the old layout until the next update).
|
|
2421
|
-
`if ((-not (Test-Path '${q}\\etc\\config.yaml')) -or (-not (Test-Path '${q}\\etc\\summrise-agent.hostname'))) { "[$(Get-Date -Format o)] migration gate FAILED (etc\\config.yaml/hostname missing) -- aborting, old version keeps running" | ${log}; try { Remove-Item -Force (
|
|
2636
|
+
`if ((-not (Test-Path '${q}\\etc\\config.yaml')) -or (-not (Test-Path '${q}\\etc\\summrise-agent.hostname'))) { "[$(Get-Date -Format o)] migration gate FAILED (etc\\config.yaml/hostname missing) -- aborting, old version keeps running" | ${log}; try { Remove-Item -Force (${busyMarkerPs()}) } catch {}; exit 1 }`,
|
|
2422
2637
|
// A running exe cannot be overwritten on Windows — stop the service
|
|
2423
2638
|
// first (task end + process kill), THEN swap with retry.
|
|
2424
2639
|
"try { Stop-ScheduledTask SummriseAgent -ErrorAction Stop } catch {}",
|
|
@@ -2442,7 +2657,7 @@ const commands = {
|
|
|
2442
2657
|
// back up (it will run the old exe until the next update).
|
|
2443
2658
|
`try { Start-ScheduledTask SummriseAgent -ErrorAction Stop } catch { schtasks /Run /TN SummriseAgent }`,
|
|
2444
2659
|
`"[$(Get-Date -Format o)] task restarted" | ${log}`,
|
|
2445
|
-
`try { Remove-Item -Force (
|
|
2660
|
+
`try { Remove-Item -Force (${busyMarkerPs()}) } catch {}`,
|
|
2446
2661
|
// Custom-port installs: the firewall rule must track the configured
|
|
2447
2662
|
// bind port (baked at update time from the live config.yaml — the
|
|
2448
2663
|
// swap itself runs from a static file and cannot read it).
|
|
@@ -2605,7 +2820,14 @@ const commands = {
|
|
|
2605
2820
|
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
2606
2821
|
now: () => Date.now(),
|
|
2607
2822
|
});
|
|
2608
|
-
const verdict = releaseMarkerVerdict({
|
|
2823
|
+
const verdict = releaseMarkerVerdict({
|
|
2824
|
+
...check,
|
|
2825
|
+
want: toVersion,
|
|
2826
|
+
// THE VERB MATTERS: this is an INSTALL that failed, so the advice names the previous version as
|
|
2827
|
+
// the thing to pin — not the one that just failed to install.
|
|
2828
|
+
verb: "update",
|
|
2829
|
+
from: fromVersion,
|
|
2830
|
+
});
|
|
2609
2831
|
if (verdict.writePin) {
|
|
2610
2832
|
console.log(`update: ${fromVersion || "?"} -> ${toVersion} COMPLETE (the device reported the new release)`);
|
|
2611
2833
|
}
|
|
@@ -2878,8 +3100,17 @@ const commands = {
|
|
|
2878
3100
|
if (regLeft.status === 0)
|
|
2879
3101
|
survivors.push("registry key HKLM\\SOFTWARE\\Summrise\\Agent");
|
|
2880
3102
|
if (survivors.length) {
|
|
2881
|
-
|
|
2882
|
-
|
|
3103
|
+
// ONE FAILURE CLASS, ONE TREATMENT. The data-dir twin forty lines below exits 1 for exactly this
|
|
3104
|
+
// ("FAILED to purge the data dir -- … is still present"), and this branch warned and exited 0 —
|
|
3105
|
+
// while the comment directly above states the rule it was breaking: `sh()` discards its result
|
|
3106
|
+
// "at every one of its call sites", so a locked file, an AV hold or a denied HKLM write "produced
|
|
3107
|
+
// 'removed' with the thing still there, AND EXIT 0".
|
|
3108
|
+
//
|
|
3109
|
+
// The program dir and the registry key are what `uninstall` was asked to remove. A warning that
|
|
3110
|
+
// exits 0 is the same false verdict the data-dir branch already refuses.
|
|
3111
|
+
console.error("uninstall: FAILED -- still present after removal:", survivors.join(", "));
|
|
3112
|
+
console.error("uninstall: a locked file or a permission problem; re-run after stopping the agent.");
|
|
3113
|
+
process.exitCode = 1;
|
|
2883
3114
|
}
|
|
2884
3115
|
else {
|
|
2885
3116
|
console.log("uninstall: program dir + registry removed");
|
|
@@ -3023,11 +3254,22 @@ const commands = {
|
|
|
3023
3254
|
// WITHOUT tripping the usage print + process.exit at module load.
|
|
3024
3255
|
if (require.main === module) {
|
|
3025
3256
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
3257
|
+
// `--version` ANSWERS THE CHECK THE INSTALLER'S OWN CHECKLIST MAKES, and it used to fail it. It is not
|
|
3258
|
+
// a verb, so it fell through to the usage branch below, printed the verb list and exited 1 — while
|
|
3259
|
+
// step 4 of `deploy/README-installer.md` BEGINS with "`summrise --version` / the panel opens". A fresh
|
|
3260
|
+
// install that worked therefore reported a failure in the one place the operator is told to look,
|
|
3261
|
+
// which is the same defect as a comment promising more than the code does.
|
|
3262
|
+
if (cmd === "--version" || cmd === "-v" || cmd === "version") {
|
|
3263
|
+
console.log(String(require("../package.json").version || ""));
|
|
3264
|
+
process.exit(0);
|
|
3265
|
+
}
|
|
3026
3266
|
if (!cmd || !commands[cmd]) {
|
|
3027
3267
|
// The list is DERIVED from `commands`, so the help cannot promise a verb that was pruned
|
|
3028
3268
|
// (`report` and `watch` were still advertised after their round-25 removal — a usage line is a
|
|
3029
3269
|
// contract, and one that lies is worse than none).
|
|
3030
|
-
console.log("summrise <" +
|
|
3270
|
+
console.log("summrise <" +
|
|
3271
|
+
Object.keys(commands).join("|") +
|
|
3272
|
+
"> -- Summrise Agent control");
|
|
3031
3273
|
Object.keys(commands).forEach((k) => console.log(" ", k));
|
|
3032
3274
|
process.exit(cmd ? 1 : 0);
|
|
3033
3275
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "summrise-agent",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.464",
|
|
4
4
|
"description": "Summrise Agent — Windows device MCP server (terminal/SSH/serial) + panel. Self-hosted npm distribution.",
|
|
5
5
|
"//build": "唯一构建路径:在本目录执行 npm run build。它是自包含的:经 npm exec 按需拉取 typescript@5,无需预装任何 node_modules(干净检出可跑;旧脚本引用 ../summrise-desktop-electron/node_modules/.bin/tsc,干净检出必 ENOENT)。必须用 npm exec --package … -- tsc 的形式:npx 会吃掉传给 tsc 的 -p/--outDir 等参数(round-435:npx 写法从未真正跑通过)。先编 electron TS(--noCheck 纯 emit,与 release.yml 一致)同步两处 src/*.js,再编 src/summrise.ts 到 bin/summrise.js 并校验 round-298 标记(弱 oracle,只证某次构建发生,新鲜性由 publish-release.sh 的 tsc 重编 + cmp 门保证)。",
|
|
6
6
|
"bin": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
],
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"scripts": {
|
|
27
|
-
"build": "npm exec --yes --package typescript@5 -- tsc -p ../summrise-desktop-electron/tsconfig.json --outDir /tmp/summrise-electron-fresh --noCheck && cp /tmp/summrise-electron-fresh/main.js ../summrise-desktop-electron/src/main.js && cp /tmp/summrise-electron-fresh/preload.js ../summrise-desktop-electron/src/preload.js && cp /tmp/summrise-electron-fresh/url-policy.js ../summrise-desktop-electron/src/url-policy.js && cp ../summrise-desktop-electron/src/main.js ../summrise-desktop-electron/src/preload.js ../summrise-desktop-electron/src/url-policy.js ./summrise-desktop-electron/src/ && npm exec --yes --package typescript@5 -- tsc -p tsconfig.json --outDir /tmp/summrise-fresh-bin && cp /tmp/summrise-fresh-bin/summrise.js bin/summrise.js && grep -q \"summrise-release\" bin/summrise.js && rm -rf /tmp/summrise-electron-fresh /tmp/summrise-fresh-bin",
|
|
27
|
+
"build": "npm exec --yes --package typescript@5.9.3 -- tsc -p ../summrise-desktop-electron/tsconfig.json --outDir /tmp/summrise-electron-fresh --noCheck && cp /tmp/summrise-electron-fresh/main.js ../summrise-desktop-electron/src/main.js && cp /tmp/summrise-electron-fresh/preload.js ../summrise-desktop-electron/src/preload.js && cp /tmp/summrise-electron-fresh/url-policy.js ../summrise-desktop-electron/src/url-policy.js && cp ../summrise-desktop-electron/src/main.js ../summrise-desktop-electron/src/preload.js ../summrise-desktop-electron/src/url-policy.js ./summrise-desktop-electron/src/ && npm exec --yes --package typescript@5.9.3 -- tsc -p tsconfig.json --outDir /tmp/summrise-fresh-bin && cp /tmp/summrise-fresh-bin/summrise.js bin/summrise.js && grep -q \"summrise-release\" bin/summrise.js && rm -rf /tmp/summrise-electron-fresh /tmp/summrise-fresh-bin",
|
|
28
28
|
"test": "node --test"
|
|
29
29
|
}
|
|
30
30
|
}
|
package/summrise-agent.exe
CHANGED
|
Binary file
|