moondesk 0.8.0 → 0.9.1

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 CHANGED
@@ -9,7 +9,7 @@ npm install -g moondesk
9
9
  ```
10
10
 
11
11
  > [!IMPORTANT]
12
- > MoonDesk runs tools on your computer with your user account's permissions. Use a VM or container when you need strong isolation.
12
+ > MoonDesk runs tools locally on your computer. Review commands before running them, and use an isolated environment for untrusted projects or code.
13
13
 
14
14
  ## Why MoonDesk?
15
15
 
package/npm/moondesk.js CHANGED
@@ -7,15 +7,18 @@ const {
7
7
  UPDATE_EXIT_CODE,
8
8
  acquireUpdateLock,
9
9
  cleanupOldUpdateVersions,
10
+ changelogNoticePath,
10
11
  compareStableVersions,
11
12
  createUpdateRequestPath,
12
13
  createUpdateStatePath,
13
14
  installExactVersion,
14
15
  installedWrapperVersion,
15
16
  readUpdateRequest,
17
+ refreshUpdateRequestToLatest,
16
18
  restartUpdatedWrapper,
17
19
  startUpdateMonitor,
18
20
  verifyInstalledWrapperVersion,
21
+ writePostUpdateNotice,
19
22
  } = require("./update-manager");
20
23
 
21
24
  function cleanManagedUpdateEnv(source = process.env) {
@@ -23,6 +26,7 @@ function cleanManagedUpdateEnv(source = process.env) {
23
26
  delete env.MOONDESK_NPM_MANAGED;
24
27
  delete env.MOONDESK_UPDATE_REQUEST_PATH;
25
28
  delete env.MOONDESK_UPDATE_STATE_PATH;
29
+ delete env.MOONDESK_CHANGELOG_NOTICE_PATH;
26
30
  return env;
27
31
  }
28
32
 
@@ -132,11 +136,14 @@ async function orchestrate(options = {}) {
132
136
  const startUpdateMonitorImpl = options.startUpdateMonitorImpl ?? startUpdateMonitor;
133
137
  const runNativeImpl = options.runNativeImpl ?? runNative;
134
138
  const readUpdateRequestImpl = options.readUpdateRequestImpl ?? readUpdateRequest;
139
+ const refreshUpdateRequestToLatestImpl =
140
+ options.refreshUpdateRequestToLatestImpl ?? refreshUpdateRequestToLatest;
135
141
  const acquireUpdateLockImpl = options.acquireUpdateLockImpl ?? acquireUpdateLock;
136
142
  const installedWrapperVersionImpl = options.installedWrapperVersionImpl ?? installedWrapperVersion;
137
143
  const installExactVersionImpl = options.installExactVersionImpl ?? installExactVersion;
138
144
  const verifyInstalledWrapperVersionImpl =
139
145
  options.verifyInstalledWrapperVersionImpl ?? verifyInstalledWrapperVersion;
146
+ const writePostUpdateNoticeImpl = options.writePostUpdateNoticeImpl ?? writePostUpdateNotice;
140
147
  const restartUpdatedWrapperImpl = options.restartUpdatedWrapperImpl ?? restartUpdatedWrapper;
141
148
  const wrapperPath = options.wrapperPath ?? __filename;
142
149
 
@@ -180,6 +187,11 @@ async function orchestrate(options = {}) {
180
187
  MOONDESK_UPDATE_REQUEST_PATH: updateRequestPath,
181
188
  MOONDESK_UPDATE_STATE_PATH: updateStatePath,
182
189
  });
190
+ try {
191
+ childEnv.MOONDESK_CHANGELOG_NOTICE_PATH = changelogNoticePath();
192
+ } catch (error) {
193
+ logger.warn?.(`MoonDesk could not prepare its optional changelog notice path: ${error.message}`);
194
+ }
183
195
  }
184
196
 
185
197
  let result;
@@ -227,7 +239,7 @@ async function orchestrate(options = {}) {
227
239
  return { code: 1, signal: null };
228
240
  }
229
241
 
230
- logger.log(`Updating MoonDesk ${request.currentVersion} -> ${request.targetVersion}...`);
242
+ let effectiveRequest = request;
231
243
  let releaseUpdateLock = null;
232
244
  let restartVersion = request.targetVersion;
233
245
  try {
@@ -236,26 +248,44 @@ async function orchestrate(options = {}) {
236
248
  env: baseEnv,
237
249
  });
238
250
 
251
+ try {
252
+ const refreshedRequest = await refreshUpdateRequestToLatestImpl(effectiveRequest);
253
+ if (compareStableVersions(refreshedRequest.targetVersion, effectiveRequest.targetVersion) > 0) {
254
+ logger.log(
255
+ `MoonDesk ${refreshedRequest.targetVersion} became available while this update was pending; updating directly to the newest version.`,
256
+ );
257
+ effectiveRequest = refreshedRequest;
258
+ restartVersion = effectiveRequest.targetVersion;
259
+ }
260
+ } catch (error) {
261
+ logger.warn?.(
262
+ `MoonDesk could not refresh the latest version before updating; continuing with ${effectiveRequest.targetVersion}: ${error.message}`,
263
+ );
264
+ }
265
+
266
+ logger.log(
267
+ `Updating MoonDesk ${effectiveRequest.currentVersion} -> ${effectiveRequest.targetVersion}...`,
268
+ );
239
269
  const alreadyInstalled = installedWrapperVersionImpl();
240
- const comparison = compareStableVersions(alreadyInstalled, request.targetVersion);
270
+ const comparison = compareStableVersions(alreadyInstalled, effectiveRequest.targetVersion);
241
271
  if (comparison < 0) {
242
- await installExactVersionImpl(request.targetVersion, {
272
+ await installExactVersionImpl(effectiveRequest.targetVersion, {
243
273
  cwd: originalCwd,
244
274
  env: baseEnv,
245
275
  });
246
- verifyInstalledWrapperVersionImpl(request.targetVersion);
276
+ verifyInstalledWrapperVersionImpl(effectiveRequest.targetVersion);
247
277
  } else if (comparison === 0) {
248
- logger.log(`MoonDesk ${request.targetVersion} was already installed by another process.`);
278
+ logger.log(`MoonDesk ${effectiveRequest.targetVersion} was already installed by another process.`);
249
279
  } else {
250
280
  restartVersion = alreadyInstalled;
251
281
  logger.log(
252
- `MoonDesk ${alreadyInstalled} is already installed, so the updater will not downgrade it to ${request.targetVersion}.`,
282
+ `MoonDesk ${alreadyInstalled} is already installed, so the updater will not downgrade it to ${effectiveRequest.targetVersion}.`,
253
283
  );
254
284
  }
255
285
  } catch (error) {
256
286
  logger.error(`MoonDesk update failed: ${error.message}`);
257
287
  logger.error(
258
- `Run 'npm install -g moondesk@${request.targetVersion}' manually to retry this exact version.`,
288
+ `Run 'npm install -g moondesk@${effectiveRequest.targetVersion}' manually to retry this exact version.`,
259
289
  );
260
290
  return { code: 1, signal: null };
261
291
  } finally {
@@ -266,6 +296,14 @@ async function orchestrate(options = {}) {
266
296
  }
267
297
  }
268
298
 
299
+ if (restartVersion === effectiveRequest.targetVersion) {
300
+ try {
301
+ writePostUpdateNoticeImpl(effectiveRequest, restartVersion);
302
+ } catch (error) {
303
+ logger.warn?.(`MoonDesk updated, but could not persist its one-time changelog notice: ${error.message}`);
304
+ }
305
+ }
306
+
269
307
  logger.log(`MoonDesk ${restartVersion} is installed. Restarting...`);
270
308
  try {
271
309
  return await restartUpdatedWrapperImpl(wrapperPath, originalArgs, {
@@ -14,16 +14,34 @@ const currentVersion = packageJson.version;
14
14
  const UPDATE_EXIT_CODE = 75;
15
15
  const UPDATE_STATE_SCHEMA_VERSION = 1;
16
16
  const UPDATE_REQUEST_SCHEMA_VERSION = 1;
17
+ const CHANGELOG_NOTICE_SCHEMA_VERSION = 1;
17
18
  const REGISTRY_LATEST_URL = "https://registry.npmjs.org/moondesk/latest";
19
+ const GITHUB_REPOSITORY = "Shattermoon/moondesk";
20
+ const GITHUB_RELEASES_API_URL = `https://api.github.com/repos/${GITHUB_REPOSITORY}/releases?per_page=20`;
21
+ const GITHUB_RELEASE_TAG_API_BASE = `https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/tags`;
22
+ const GITHUB_RELEASE_WEB_BASE = `https://github.com/${GITHUB_REPOSITORY}/releases/tag`;
23
+ const GITHUB_PULL_WEB_BASE = `https://github.com/${GITHUB_REPOSITORY}/pull`;
18
24
  const UPDATE_CHECK_INTERVAL_MS = 15 * 60_000;
19
25
  const UPDATE_CHECK_TIMEOUT_MS = 15_000;
20
26
  const MAX_UPDATE_METADATA_BYTES = 64 * 1024;
27
+ const MAX_CHANGELOG_METADATA_BYTES = 512 * 1024;
28
+ const MAX_CHANGELOG_ITEMS = 12;
29
+ const MAX_CHANGELOG_ITEM_CHARS = 180;
21
30
  const MAX_UPDATE_REQUEST_BYTES = 16 * 1024;
22
31
  const MAX_NPM_ROOT_BYTES = 16 * 1024;
23
32
  const NPM_ROOT_TIMEOUT_MS = 10_000;
24
33
  const UPDATE_LOCK_WAIT_MS = 60_000;
25
34
  const UPDATE_LOCK_POLL_MS = 200;
26
35
 
36
+ function escapeRegExp(value) {
37
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
38
+ }
39
+
40
+ const GITHUB_PULL_ATTRIBUTION_RE = new RegExp(
41
+ `\\s+by\\s+@[A-Za-z0-9_-]+\\s+in\\s+${escapeRegExp(GITHUB_PULL_WEB_BASE)}\\/\\d+\\s*$`,
42
+ "i",
43
+ );
44
+
27
45
  function parseStableVersion(input) {
28
46
  if (typeof input !== "string") {
29
47
  return null;
@@ -101,6 +119,49 @@ function cleanupOldUpdateVersions(options = {}) {
101
119
  return { removed, skipped };
102
120
  }
103
121
 
122
+ function changelogNoticePath(version = currentVersion) {
123
+ if (!parseStableVersion(version)) {
124
+ throw new Error(`MoonDesk changelog version must be a stable semantic version: ${version}`);
125
+ }
126
+ return path.join(updateRootDir(), `v${version}`, "post-update.json");
127
+ }
128
+
129
+ function normalizePersistedReleaseNotes(value) {
130
+ if (!Array.isArray(value)) return [];
131
+ const notes = [];
132
+ for (const item of value) {
133
+ if (typeof item !== "string") continue;
134
+ const trimmed = item.trim();
135
+ if (!trimmed || trimmed.length > MAX_CHANGELOG_ITEM_CHARS) continue;
136
+ notes.push(trimmed);
137
+ if (notes.length >= MAX_CHANGELOG_ITEMS) break;
138
+ }
139
+ return notes;
140
+ }
141
+
142
+ function normalizeReleaseUrl(value, version) {
143
+ const expected = `${GITHUB_RELEASE_WEB_BASE}/v${version}`;
144
+ return typeof value === "string" && value === expected ? value : null;
145
+ }
146
+
147
+ function writePostUpdateNotice(request, installedVersion, options = {}) {
148
+ if (!request || request.targetVersion !== installedVersion || !parseStableVersion(installedVersion)) {
149
+ return null;
150
+ }
151
+ const filePath = options.noticePath ?? changelogNoticePath(installedVersion);
152
+ const notice = {
153
+ schemaVersion: CHANGELOG_NOTICE_SCHEMA_VERSION,
154
+ packageName: "moondesk",
155
+ fromVersion: request.currentVersion,
156
+ toVersion: installedVersion,
157
+ releaseNotes: normalizePersistedReleaseNotes(request.releaseNotes),
158
+ releaseUrl: normalizeReleaseUrl(request.releaseUrl, installedVersion),
159
+ createdAt: new Date().toISOString(),
160
+ };
161
+ atomicWriteJson(filePath, notice, options);
162
+ return filePath;
163
+ }
164
+
104
165
  function updateRequestDir() {
105
166
  return path.join(currentUpdateDir(), "requests");
106
167
  }
@@ -234,7 +295,7 @@ function atomicWriteJson(filePath, value, options = {}) {
234
295
  }
235
296
  }
236
297
 
237
- async function fetchJsonLimited(fetchImpl, url, externalSignal) {
298
+ async function fetchJsonLimited(fetchImpl, url, externalSignal, maxBytes = MAX_UPDATE_METADATA_BYTES) {
238
299
  const controller = new AbortController();
239
300
  const abortFromParent = () => controller.abort();
240
301
  if (externalSignal) {
@@ -259,7 +320,7 @@ async function fetchJsonLimited(fetchImpl, url, externalSignal) {
259
320
  throw new Error(`${url} returned HTTP ${response.status}`);
260
321
  }
261
322
  const contentLength = Number(response.headers.get("content-length"));
262
- if (Number.isFinite(contentLength) && contentLength > MAX_UPDATE_METADATA_BYTES) {
323
+ if (Number.isFinite(contentLength) && contentLength > maxBytes) {
263
324
  controller.abort();
264
325
  throw new Error(`MoonDesk update metadata is unexpectedly large (${contentLength} bytes)`);
265
326
  }
@@ -272,7 +333,7 @@ async function fetchJsonLimited(fetchImpl, url, externalSignal) {
272
333
  for await (const chunk of response.body) {
273
334
  const buffer = Buffer.from(chunk);
274
335
  totalBytes += buffer.length;
275
- if (totalBytes > MAX_UPDATE_METADATA_BYTES) {
336
+ if (totalBytes > maxBytes) {
276
337
  controller.abort();
277
338
  throw new Error("MoonDesk update metadata exceeded the download limit");
278
339
  }
@@ -285,13 +346,138 @@ async function fetchJsonLimited(fetchImpl, url, externalSignal) {
285
346
  }
286
347
  }
287
348
 
288
- async function checkForUpdate(options = {}) {
349
+ function normalizeChangelogLine(line) {
350
+ if (typeof line !== "string") return null;
351
+ let value = line.trim();
352
+ if (!value || value.startsWith("#") || /^\*\*Full Changelog\*\*/i.test(value)) return null;
353
+ value = value.replace(/^[-*+]\s+/, "");
354
+ value = value.replace(GITHUB_PULL_ATTRIBUTION_RE, "");
355
+ value = value.replace(/^\[(.+?)\]\([^)]*\)$/, "$1");
356
+ value = value.replace(/`([^`]+)`/g, "$1");
357
+ value = value.replace(/^(?:feat|fix|chore|refactor|perf|docs|test|build|ci|style)(?:\([^)]*\))?!?:\s*/i, "");
358
+ value = value.trim();
359
+ if (!value || /^https?:\/\//i.test(value)) return null;
360
+ value = `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
361
+ if (value.length > MAX_CHANGELOG_ITEM_CHARS) {
362
+ value = `${value.slice(0, MAX_CHANGELOG_ITEM_CHARS - 3).trimEnd()}...`;
363
+ }
364
+ return value;
365
+ }
366
+
367
+ function normalizeReleaseNotes(body) {
368
+ if (typeof body !== "string") return [];
369
+ const notes = [];
370
+ const seen = new Set();
371
+ for (const line of body.split(/\r?\n/)) {
372
+ const note = normalizeChangelogLine(line);
373
+ if (!note || seen.has(note)) continue;
374
+ seen.add(note);
375
+ notes.push(note);
376
+ if (notes.length >= MAX_CHANGELOG_ITEMS) break;
377
+ }
378
+ return notes;
379
+ }
380
+
381
+ function boundedChangelogItem(value) {
382
+ const trimmed = String(value ?? "").trim();
383
+ if (!trimmed) return null;
384
+ if (trimmed.length <= MAX_CHANGELOG_ITEM_CHARS) return trimmed;
385
+ return `${trimmed.slice(0, MAX_CHANGELOG_ITEM_CHARS - 3).trimEnd()}...`;
386
+ }
387
+
388
+ function stableReleaseVersion(release) {
389
+ if (!release || release.draft === true || release.prerelease === true) return null;
390
+ if (typeof release.tag_name !== "string" || !release.tag_name.startsWith("v")) return null;
391
+ const version = release.tag_name.slice(1);
392
+ return parseStableVersion(version) ? version : null;
393
+ }
394
+
395
+ async function fetchReleaseChangelog(fromVersion, toVersion, options = {}) {
396
+ if (
397
+ !parseStableVersion(fromVersion) ||
398
+ !parseStableVersion(toVersion) ||
399
+ compareStableVersions(toVersion, fromVersion) <= 0
400
+ ) {
401
+ return { releaseNotes: [], releaseUrl: null };
402
+ }
403
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
404
+ if (typeof fetchImpl !== "function") return { releaseNotes: [], releaseUrl: null };
405
+
406
+ const releasesUrl = options.releasesApiUrl ?? GITHUB_RELEASES_API_URL;
407
+ const releases = await fetchJsonLimited(
408
+ fetchImpl,
409
+ releasesUrl,
410
+ options.signal,
411
+ MAX_CHANGELOG_METADATA_BYTES,
412
+ );
413
+ if (!Array.isArray(releases)) {
414
+ throw new Error("GitHub returned invalid MoonDesk releases metadata");
415
+ }
416
+
417
+ const selected = [];
418
+ for (const release of releases) {
419
+ const version = stableReleaseVersion(release);
420
+ if (!version) continue;
421
+ if (
422
+ compareStableVersions(version, fromVersion) > 0 &&
423
+ compareStableVersions(version, toVersion) <= 0
424
+ ) {
425
+ selected.push({ release, version });
426
+ }
427
+ }
428
+
429
+ if (!selected.some((item) => item.version === toVersion)) {
430
+ const tagApiBase = options.releaseTagApiBase ?? GITHUB_RELEASE_TAG_API_BASE;
431
+ try {
432
+ const release = await fetchJsonLimited(
433
+ fetchImpl,
434
+ `${tagApiBase}/v${toVersion}`,
435
+ options.signal,
436
+ MAX_CHANGELOG_METADATA_BYTES,
437
+ );
438
+ if (stableReleaseVersion(release) === toVersion) {
439
+ selected.push({ release, version: toVersion });
440
+ }
441
+ } catch {
442
+ // The recent releases list may briefly lag the just-published npm tag.
443
+ // Missing notes must never block a valid npm update.
444
+ }
445
+ }
446
+
447
+ selected.sort((left, right) => compareStableVersions(right.version, left.version));
448
+ const uniqueReleases = [];
449
+ const seenVersions = new Set();
450
+ for (const item of selected) {
451
+ if (seenVersions.has(item.version)) continue;
452
+ seenVersions.add(item.version);
453
+ uniqueReleases.push(item);
454
+ }
455
+
456
+ const includeVersion = uniqueReleases.length > 1;
457
+ const releaseNotes = [];
458
+ const seenNotes = new Set();
459
+ for (const { release, version } of uniqueReleases) {
460
+ for (const note of normalizeReleaseNotes(release.body)) {
461
+ const rendered = boundedChangelogItem(includeVersion ? `v${version}: ${note}` : note);
462
+ if (!rendered || seenNotes.has(rendered)) continue;
463
+ seenNotes.add(rendered);
464
+ releaseNotes.push(rendered);
465
+ if (releaseNotes.length >= MAX_CHANGELOG_ITEMS) break;
466
+ }
467
+ if (releaseNotes.length >= MAX_CHANGELOG_ITEMS) break;
468
+ }
469
+
470
+ const expectedUrl = `${GITHUB_RELEASE_WEB_BASE}/v${toVersion}`;
471
+ const target = uniqueReleases.find((item) => item.version === toVersion)?.release;
472
+ const releaseUrl = target ? expectedUrl : null;
473
+ return { releaseNotes, releaseUrl };
474
+ }
475
+
476
+ async function fetchLatestPackageMetadata(options = {}) {
289
477
  const fetchImpl = options.fetchImpl ?? globalThis.fetch;
290
- const statePath = options.statePath ?? createUpdateStatePath();
291
478
  const registryUrl = options.registryUrl ?? REGISTRY_LATEST_URL;
292
- const managedInstall = options.managedInstall === true;
293
479
  if (typeof fetchImpl !== "function") {
294
- return null;
480
+ throw new Error("MoonDesk update checks require a fetch implementation");
295
481
  }
296
482
 
297
483
  const metadata = await fetchJsonLimited(fetchImpl, registryUrl, options.signal);
@@ -305,8 +491,69 @@ async function checkForUpdate(options = {}) {
305
491
  if (typeof metadata?.dist?.integrity !== "string" || !metadata.dist.integrity.startsWith("sha512-")) {
306
492
  throw new Error("npm returned MoonDesk metadata without a sha512 package integrity value");
307
493
  }
494
+ return metadata;
495
+ }
496
+
497
+ async function refreshUpdateRequestToLatest(request, options = {}) {
498
+ if (
499
+ !request ||
500
+ request.currentVersion !== currentVersion ||
501
+ !parseStableVersion(request.targetVersion) ||
502
+ compareStableVersions(request.targetVersion, currentVersion) <= 0
503
+ ) {
504
+ throw new Error("Refusing to refresh an invalid MoonDesk update request");
505
+ }
308
506
 
507
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
508
+ const metadata = await fetchLatestPackageMetadata({ ...options, fetchImpl });
509
+ const latestVersion = metadata.version;
510
+ if (compareStableVersions(latestVersion, request.targetVersion) <= 0) {
511
+ return request;
512
+ }
513
+
514
+ let releaseNotes = [];
515
+ let releaseUrl = null;
516
+ try {
517
+ ({ releaseNotes, releaseUrl } = await fetchReleaseChangelog(
518
+ request.currentVersion,
519
+ latestVersion,
520
+ { ...options, fetchImpl },
521
+ ));
522
+ } catch {
523
+ // The exact newest npm target is authoritative. GitHub notes remain optional.
524
+ }
525
+
526
+ return {
527
+ ...request,
528
+ targetVersion: latestVersion,
529
+ releaseNotes,
530
+ releaseUrl,
531
+ };
532
+ }
533
+
534
+ async function checkForUpdate(options = {}) {
535
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
536
+ const statePath = options.statePath ?? createUpdateStatePath();
537
+ const managedInstall = options.managedInstall === true;
538
+ if (typeof fetchImpl !== "function") {
539
+ return null;
540
+ }
541
+
542
+ const metadata = await fetchLatestPackageMetadata({ ...options, fetchImpl });
543
+ const latestVersion = metadata.version;
309
544
  const available = managedInstall && compareStableVersions(latestVersion, currentVersion) > 0;
545
+ let releaseNotes = [];
546
+ let releaseUrl = null;
547
+ if (available) {
548
+ try {
549
+ ({ releaseNotes, releaseUrl } = await fetchReleaseChangelog(currentVersion, latestVersion, {
550
+ ...options,
551
+ fetchImpl,
552
+ }));
553
+ } catch {
554
+ // Release notes are optional metadata. A GitHub outage must never hide a valid npm update.
555
+ }
556
+ }
310
557
  const state = {
311
558
  schemaVersion: UPDATE_STATE_SCHEMA_VERSION,
312
559
  packageName: "moondesk",
@@ -314,6 +561,8 @@ async function checkForUpdate(options = {}) {
314
561
  latestVersion,
315
562
  managedInstall,
316
563
  available,
564
+ releaseNotes,
565
+ releaseUrl,
317
566
  checkedAt: new Date().toISOString(),
318
567
  };
319
568
  atomicWriteJson(statePath, state, options);
@@ -396,7 +645,11 @@ function readUpdateRequest(requestPath) {
396
645
  ) {
397
646
  return null;
398
647
  }
399
- return parsed;
648
+ return {
649
+ ...parsed,
650
+ releaseNotes: normalizePersistedReleaseNotes(parsed.releaseNotes),
651
+ releaseUrl: normalizeReleaseUrl(parsed.releaseUrl, parsed.targetVersion),
652
+ };
400
653
  }
401
654
 
402
655
  function updateLockPath() {
@@ -570,17 +823,22 @@ module.exports = {
570
823
  atomicWriteJson,
571
824
  checkForUpdate,
572
825
  cleanupOldUpdateVersions,
826
+ changelogNoticePath,
573
827
  compareStableVersions,
574
828
  createUpdateRequestPath,
575
829
  createUpdateStatePath,
576
830
  currentVersion,
831
+ fetchReleaseChangelog,
577
832
  installExactVersion,
578
833
  installedWrapperVersion,
579
834
  isGlobalPackageInstall,
835
+ normalizeReleaseNotes,
580
836
  parseStableVersion,
581
837
  readUpdateRequest,
838
+ refreshUpdateRequestToLatest,
582
839
  resolveGlobalNpmRoot,
583
840
  restartUpdatedWrapper,
584
841
  startUpdateMonitor,
585
842
  verifyInstalledWrapperVersion,
843
+ writePostUpdateNotice,
586
844
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moondesk",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "description": "MoonDesk — use ChatGPT Chat as a local coding agent, by Shattermoon.",
5
5
  "author": "Shattermoon",
6
6
  "license": "MIT",