blun-king-cli 9.1.41 → 9.1.43

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/LIESMICH.txt CHANGED
@@ -9,7 +9,7 @@ Installation
9
9
  ------------
10
10
  Die geprüfte Version exakt global installieren:
11
11
 
12
- npm install -g blun-king-cli@9.1.36
12
+ npm install -g blun-king-cli@9.1.43
13
13
 
14
14
  Start
15
15
  -----
@@ -20,6 +20,17 @@ Start
20
20
  Beide Startbefehle verwenden dieselbe Version, dasselbe Konto, dasselbe Modell
21
21
  und dieselben Befehle.
22
22
 
23
+ Zug-Wächter
24
+ -----------
25
+ Läuft ein Zug 20 Minuten ohne neues Werkzeugergebnis, meldet die Konsole den
26
+ Stand auch im verbundenen Telegram-Kanal. Fünf aufeinanderfolgende identische
27
+ fehlgeschlagene Werkzeugaufrufe beenden den Zug mit einer Fehlermeldung.
28
+
29
+ Die Grenzwerte lassen sich vor dem Start über diese Umgebungsvariablen ändern:
30
+
31
+ BLUN_TURN_WATCHDOG_IDLE_MINUTES
32
+ BLUN_TURN_WATCHDOG_MAX_FAILED_REPETITIONS
33
+
23
34
  Nachweisbare Arbeitsabläufe
24
35
  ---------------------------
25
36
 
package/README.md CHANGED
@@ -9,7 +9,7 @@ Voraussetzung ist Node.js 24.15 oder neuer. Die geprüfte Version wird exakt
9
9
  installiert:
10
10
 
11
11
  ```powershell
12
- npm install -g blun-king-cli@9.1.36
12
+ npm install -g blun-king-cli@9.1.43
13
13
  ```
14
14
 
15
15
  ## Reproduzierbares Staging und Packen
@@ -35,6 +35,16 @@ an. Version, Konto, Modell und Befehle sind ansonsten identisch. Der Unterschied
35
35
  gilt nur für den laufenden Prozess; die gespeicherte
36
36
  Plugin-Konfiguration wird nicht umgeschrieben.
37
37
 
38
+ ## Zug-Wächter
39
+
40
+ Läuft ein Zug 20 Minuten ohne neues Werkzeugergebnis, meldet die Konsole den
41
+ Stand auch im verbundenen Telegram-Kanal. Fünf aufeinanderfolgende identische
42
+ fehlgeschlagene Werkzeugaufrufe beenden den Zug mit einer Fehlermeldung.
43
+
44
+ Die Grenzwerte lassen sich vor dem Start mit
45
+ `BLUN_TURN_WATCHDOG_IDLE_MINUTES` und
46
+ `BLUN_TURN_WATCHDOG_MAX_FAILED_REPETITIONS` ändern.
47
+
38
48
  Beim ersten Start werden das Telegram-Plugin und die mitgelieferten Skills
39
49
  eingerichtet. Die Anmeldung erfolgt anschließend in der
40
50
  Konsole mit `/login` über den BLUN-OAuth-Server. Das Paket erzeugt keine
@@ -26,6 +26,7 @@ const STATE_FILE = 'update-notice.json';
26
26
  const UPDATE_SKIP_FILE = 'update-skip-version.json';
27
27
  const PENDING_UPDATE_FILE = 'update-pending.json';
28
28
  const UPDATE_DIRECTORY = 'updates';
29
+ const CORE_INSTALL_STATE_FILE = 'install.json';
29
30
  const UPDATE_KEY_ESCAPE_TIMEOUT_MS = 500;
30
31
  const UPDATE_SIGNAL_EXIT_CODES = Object.freeze({
31
32
  SIGHUP: 129,
@@ -45,14 +46,25 @@ const SEMVER_PATTERN = new RegExp(
45
46
  + `(?:-(${SEMVER_IDENTIFIER}(?:\\.${SEMVER_IDENTIFIER})*))?`
46
47
  + '(?:\\+([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?$',
47
48
  );
49
+ const LEGACY_MANIFEST_KEYS = Object.freeze([
50
+ 'install',
51
+ 'latest',
52
+ 'minSupported',
53
+ 'name',
54
+ 'notesUrl',
55
+ 'releasedAt',
56
+ ]);
48
57
  const MANIFEST_KEYS = Object.freeze([
49
58
  'install',
50
59
  'latest',
51
60
  'minSupported',
52
61
  'name',
53
62
  'notesUrl',
63
+ 'releaseNotes',
54
64
  'releasedAt',
55
65
  ]);
66
+ const RELEASE_NOTES_MAX_COUNT = 8;
67
+ const RELEASE_NOTE_MAX_BYTES = 240;
56
68
  const ENDPOINT_LIMITS = new Map([
57
69
  [REGISTRY_URL, 1024 * 1024],
58
70
  [FALLBACK_MANIFEST_URL, 64 * 1024],
@@ -192,9 +204,23 @@ function isIsoTimestamp(value) {
192
204
  && date.getUTCSeconds() === Number(match[6]);
193
205
  }
194
206
 
207
+ function parseReleaseNotes(value) {
208
+ if (!Array.isArray(value) || value.length > RELEASE_NOTES_MAX_COUNT) return undefined;
209
+ const notes = [];
210
+ for (const note of value) {
211
+ if (!isBoundedText(note, RELEASE_NOTE_MAX_BYTES)) return undefined;
212
+ notes.push(note);
213
+ }
214
+ return Object.freeze(notes);
215
+ }
216
+
195
217
  function parseFallbackManifest(value) {
196
- const record = exactDataRecord(value, MANIFEST_KEYS);
218
+ const record = exactDataRecord(value, MANIFEST_KEYS)
219
+ || exactDataRecord(value, LEGACY_MANIFEST_KEYS);
197
220
  const notesUrl = record ? canonicalOfficialNotesUrl(record.notesUrl) : undefined;
221
+ const releaseNotes = record && Object.hasOwn(record, 'releaseNotes')
222
+ ? parseReleaseNotes(record.releaseNotes)
223
+ : undefined;
198
224
  if (!record
199
225
  || record.name !== PACKAGE_NAME
200
226
  || !parseSemver(record.latest)
@@ -202,6 +228,7 @@ function parseFallbackManifest(value) {
202
228
  || compareSemver(record.minSupported, record.latest) === 1
203
229
  || !isIsoTimestamp(record.releasedAt)
204
230
  || !notesUrl
231
+ || (Object.hasOwn(record, 'releaseNotes') && releaseNotes === undefined)
205
232
  || !isBoundedText(record.install, 256)) {
206
233
  return undefined;
207
234
  }
@@ -211,6 +238,7 @@ function parseFallbackManifest(value) {
211
238
  minSupported: record.minSupported,
212
239
  releasedAt: record.releasedAt,
213
240
  notesUrl,
241
+ ...(releaseNotes === undefined ? {} : { releaseNotes }),
214
242
  install: record.install,
215
243
  });
216
244
  }
@@ -243,7 +271,14 @@ function resolveEffectiveRelease(currentVersion, registryTags, fallbackManifest)
243
271
  if (compareSemver(version, currentVersion) <= 0) return undefined;
244
272
 
245
273
  if (validFallback && compareSemver(validFallback.latest, version) === 0) {
246
- return { channel: 'stable', version, notesUrl: validFallback.notesUrl };
274
+ return {
275
+ channel: 'stable',
276
+ version,
277
+ notesUrl: validFallback.notesUrl,
278
+ ...(validFallback.releaseNotes === undefined
279
+ ? {}
280
+ : { releaseNotes: validFallback.releaseNotes }),
281
+ };
247
282
  }
248
283
  return { channel: 'stable', version };
249
284
  }
@@ -495,6 +530,50 @@ function clearPendingUpdate(blunDir) {
495
530
  fs.rmSync(pendingUpdatePath(blunDir), { force: true });
496
531
  }
497
532
 
533
+ function writeCoreInstallSuccess(
534
+ blunDir,
535
+ fromVersion,
536
+ version,
537
+ releaseNotes,
538
+ now = Date.now(),
539
+ ) {
540
+ if (!parseSemver(fromVersion) || !parseSemver(version)) {
541
+ throw new Error('INVALID_UPDATE_VERSION');
542
+ }
543
+ const updateDirectory = path.join(blunDir, UPDATE_DIRECTORY);
544
+ ensurePrivateDirectory(blunDir);
545
+ ensurePrivateDirectory(updateDirectory);
546
+ const filePath = path.join(updateDirectory, CORE_INSTALL_STATE_FILE);
547
+ const temporaryPath = path.join(
548
+ updateDirectory,
549
+ `.${CORE_INSTALL_STATE_FILE}.${process.pid}.${randomUUID()}.tmp`,
550
+ );
551
+ try {
552
+ writePrivateFile(temporaryPath, `${JSON.stringify({
553
+ active: null,
554
+ lastFailure: null,
555
+ lastSuccess: {
556
+ version,
557
+ fromVersion,
558
+ ...(releaseNotes === undefined ? {} : { releaseNotes }),
559
+ installedAt: new Date(now).toISOString(),
560
+ notifiedAt: null,
561
+ },
562
+ })}\n`);
563
+ const handle = fs.openSync(temporaryPath, 'r+');
564
+ try {
565
+ fs.fsyncSync(handle);
566
+ } finally {
567
+ fs.closeSync(handle);
568
+ }
569
+ fs.renameSync(temporaryPath, filePath);
570
+ securePrivateFile(filePath);
571
+ } catch (error) {
572
+ fs.rmSync(temporaryPath, { force: true });
573
+ throw error;
574
+ }
575
+ }
576
+
498
577
  function isInteractiveUpdateStart({ argv, env, stdin, stdout }) {
499
578
  const isOrdinaryStartup = Array.isArray(argv)
500
579
  && (argv.length === 0
@@ -1178,10 +1257,10 @@ function noReleaseReason(currentVersion, registryTags, fallbackManifest) {
1178
1257
  return parseSemver(latest) || fallbackManifest ? 'current' : 'unavailable';
1179
1258
  }
1180
1259
 
1181
- function explicitNoTarget(stdout, reason) {
1260
+ function explicitNoTarget(stdout, reason, currentVersion) {
1182
1261
  stdout.write(reason === 'current'
1183
- ? 'Kein Update verfügbar. BLUN Code ist auf dem aktuellen Updatekanal.\n'
1184
- : 'Kein sicherer Update-Zielstand verfügbar.\n');
1262
+ ? `Kein Update verfügbar. Installiert: ${currentVersion} · Aktuell: ${currentVersion}.\n`
1263
+ : `Kein sicherer Update-Zielstand verfügbar. Installiert: ${currentVersion}.\n`);
1185
1264
  return { kind: 'current', reason };
1186
1265
  }
1187
1266
 
@@ -1280,7 +1359,7 @@ async function runUpdateFlow(options, explicitUpdate) {
1280
1359
  sources = await (options.loadReleaseSources || loadReleaseSources)();
1281
1360
  } catch {
1282
1361
  return explicitUpdate
1283
- ? explicitNoTarget(stdout, 'unavailable')
1362
+ ? explicitNoTarget(stdout, 'unavailable', currentVersion)
1284
1363
  : { kind: 'continue', reason: 'unavailable' };
1285
1364
  }
1286
1365
  const registryTags = parseRegistryDocument(sources?.registryDocument);
@@ -1289,7 +1368,7 @@ async function runUpdateFlow(options, explicitUpdate) {
1289
1368
  if (!release) {
1290
1369
  const reason = noReleaseReason(currentVersion, registryTags, fallbackManifest);
1291
1370
  return explicitUpdate
1292
- ? explicitNoTarget(stdout, reason)
1371
+ ? explicitNoTarget(stdout, reason, currentVersion)
1293
1372
  : { kind: 'continue', reason };
1294
1373
  }
1295
1374
  const queuedForRelease = pendingVersion === release.version;
@@ -1413,7 +1492,23 @@ async function runUpdateFlow(options, explicitUpdate) {
1413
1492
  if (installResult?.code === 0 && installResult?.verified === true) {
1414
1493
  updateProgress(4, 'Installation geprüft');
1415
1494
  clearPendingUpdate(blunDir);
1416
- stdout.write('Update erfolgreich installiert. Starte BLUN Code neu, um die neue Version zu verwenden.\n');
1495
+ try {
1496
+ writeCoreInstallSuccess(
1497
+ blunDir,
1498
+ currentVersion,
1499
+ release.version,
1500
+ release.releaseNotes,
1501
+ (options.now || Date.now)(),
1502
+ );
1503
+ } catch {
1504
+ stderr.write(
1505
+ `Update ${release.version} wurde installiert, aber die Startmeldung konnte nicht gespeichert werden.\n`,
1506
+ );
1507
+ }
1508
+ stdout.write(
1509
+ `Update ${currentVersion} -> ${release.version} erfolgreich installiert. Starte BLUN Code neu, um die neue Version zu verwenden.\n`,
1510
+ );
1511
+ if (release.notesUrl) stdout.write(`Versionshinweise: ${release.notesUrl}\n`);
1417
1512
  return { kind: 'installed', version: release.version };
1418
1513
  }
1419
1514