dsh-home-hosted 0.4.0 → 0.4.2

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/lib/index.js CHANGED
@@ -1974,39 +1974,53 @@ function expandHome(value) {
1974
1974
  return path6.join(os3.homedir(), value.slice(2));
1975
1975
  return value;
1976
1976
  }
1977
+ function resolveUnder(value, homeDir) {
1978
+ if (value === "~")
1979
+ return path6.resolve(homeDir);
1980
+ if (value.startsWith("~/") || value.startsWith("~\\"))
1981
+ return path6.resolve(path6.join(homeDir, value.slice(2)));
1982
+ return path6.isAbsolute(value) ? path6.resolve(value) : path6.resolve(homeDir, value);
1983
+ }
1977
1984
  function dshHome() {
1978
1985
  const configured = process.env.DSH_HOME?.trim();
1979
1986
  return configured ? path6.resolve(expandHome(configured)) : path6.join(os3.homedir(), ".dsh");
1980
1987
  }
1981
- function homeHostedHome() {
1982
- const configured = process.env.HHOSTED_HOME?.trim();
1983
- return configured ? path6.resolve(expandHome(configured)) : path6.join(os3.homedir(), ".home-hosted");
1984
- }
1985
1988
  function resolveHomeHostedHome(stateDir, env = process.env, homeDir = os3.homedir()) {
1986
1989
  const configured = env.HHOSTED_HOME?.trim();
1987
1990
  if (configured)
1988
- return { home: path6.resolve(expandHome(configured)), source: "env" };
1989
- if (hasPanelState(path6.join(homeDir, ".home-hosted")))
1990
- return { home: path6.join(homeDir, ".home-hosted"), source: "legacy" };
1991
- return { home: path6.join(path6.resolve(expandHome(stateDir)), "panel"), source: "instance" };
1991
+ return { home: resolveUnder(configured, homeDir), source: "env" };
1992
+ const legacy = path6.join(homeDir, ".home-hosted");
1993
+ if (hasLivePanel(legacy))
1994
+ return { home: legacy, source: "legacy" };
1995
+ return { home: path6.join(resolveUnder(stateDir, homeDir), "panel"), source: "instance" };
1992
1996
  }
1993
- function hasPanelState(home) {
1994
- return ["servers.config.json", "run.json"].some((file) => {
1995
- try {
1996
- return fs7.statSync(path6.join(home, file)).isFile();
1997
- } catch {
1997
+ function hasLivePanel(home) {
1998
+ try {
1999
+ if (fs7.statSync(path6.join(home, "servers.config.json")).isFile())
2000
+ return true;
2001
+ } catch {
2002
+ }
2003
+ try {
2004
+ const raw = JSON.parse(fs7.readFileSync(path6.join(home, "run.json"), "utf8"));
2005
+ if (typeof raw.pid !== "number")
1998
2006
  return false;
1999
- }
2000
- });
2007
+ process.kill(raw.pid, 0);
2008
+ return true;
2009
+ } catch {
2010
+ return false;
2011
+ }
2001
2012
  }
2002
2013
  function bootUnitName(stateDir) {
2003
- if (isDefaultStateDir(stateDir))
2014
+ if (isMachineDefaultStateDir(stateDir))
2004
2015
  return "home-hosted";
2005
- return `home-hosted-${crypto.createHash("sha256").update(path6.resolve(stateDir)).digest("hex").slice(0, 8)}`;
2016
+ const key = path6.resolve(expandHome(stateDir?.trim() ?? ""));
2017
+ return `home-hosted-${crypto.createHash("sha256").update(key).digest("hex").slice(0, 8)}`;
2006
2018
  }
2007
- function isDefaultStateDir(stateDir) {
2019
+ function isMachineDefaultStateDir(stateDir) {
2008
2020
  const bare = stateDir?.trim();
2009
- return bare === void 0 || bare.length === 0 || path6.resolve(expandHome(bare)) === path6.join(dshHome(), "dsh-home-hosted");
2021
+ if (bare === void 0 || bare.length === 0)
2022
+ return true;
2023
+ return path6.resolve(expandHome(bare)) === path6.join(os3.homedir(), ".dsh", "dsh-home-hosted");
2010
2024
  }
2011
2025
  function pluginStateDir(override) {
2012
2026
  const configured = override?.trim();
@@ -2014,13 +2028,13 @@ function pluginStateDir(override) {
2014
2028
  return path6.resolve(expandHome(configured));
2015
2029
  return path6.join(dshHome(), "dsh-home-hosted");
2016
2030
  }
2017
- function runtimeFile(home = homeHostedHome()) {
2031
+ function runtimeFile(home) {
2018
2032
  return path6.join(home, "run.json");
2019
2033
  }
2020
- function secretsFile(home = homeHostedHome()) {
2034
+ function secretsFile(home) {
2021
2035
  return path6.join(home, ".control-secrets.json");
2022
2036
  }
2023
- function configFile(home = homeHostedHome()) {
2037
+ function configFile(home) {
2024
2038
  return path6.join(home, "servers.config.json");
2025
2039
  }
2026
2040
 
@@ -2081,6 +2095,7 @@ import process7 from "node:process";
2081
2095
  import fs9 from "node:fs";
2082
2096
  import path7 from "node:path";
2083
2097
  import process4 from "node:process";
2098
+ var LEGACY_BOOT_UNIT_NAME = "home-hosted";
2084
2099
  async function which(command) {
2085
2100
  const probe = process4.platform === "win32" ? "where.exe" : "which";
2086
2101
  const result = await run(probe, [command], { timeoutMs: 5e3 });
@@ -2138,8 +2153,8 @@ function buildHomeHostedBootSpec(facts, launch, options) {
2138
2153
  env: homeHostedEnv(facts, launch),
2139
2154
  marker: options.marker ?? "managed by dsh-home-hosted",
2140
2155
  // One artifact per state root, so two plugin instances cannot overwrite or
2141
- // delete each other's entry. The default install keeps the historical name.
2142
- unitName: bootUnitName(options.stateDir),
2156
+ // delete each other's entry. The machine-default install keeps the old name.
2157
+ unitName: options.unitName ?? bootUnitName(options.stateDir),
2143
2158
  label: "home-hosted control panel",
2144
2159
  logDir
2145
2160
  };
@@ -2836,10 +2851,48 @@ function readdirNames(dir) {
2836
2851
  return [];
2837
2852
  }
2838
2853
  }
2854
+ function projectDshEntry(dir, depth = 4) {
2855
+ let current = resolveDir(dir);
2856
+ for (let hop = 0; hop <= depth; hop += 1) {
2857
+ const shim = path10.join(current, "node_modules", ".bin", "dsh");
2858
+ const shimmed = resolveShimmedCli(shim, process7.execPath);
2859
+ if (shimmed?.cliEntry != null && isEntry(shimmed.cliEntry))
2860
+ return shimmed.cliEntry;
2861
+ const found = dshCandidatesUnder(current).map((entry) => ({ entry, version: versionOf(entry) })).sort((a, b) => compareVersions(b.version ?? "0.0.0", a.version ?? "0.0.0"));
2862
+ if (found[0] !== void 0)
2863
+ return found[0].entry;
2864
+ const parent = path10.dirname(current);
2865
+ if (parent === current)
2866
+ break;
2867
+ current = parent;
2868
+ }
2869
+ return null;
2870
+ }
2871
+ function resolveDir(dir) {
2872
+ try {
2873
+ return fs12.realpathSync(dir);
2874
+ } catch {
2875
+ return path10.resolve(dir);
2876
+ }
2877
+ }
2839
2878
  async function resolveDshLaunch(options = {}) {
2840
2879
  const argv1 = options.argv1 === void 0 ? process7.argv[1] : options.argv1;
2841
2880
  const dshHome2 = options.dshHome ?? dshHome();
2842
2881
  const stateDir = options.stateDir ?? pluginStateDir();
2882
+ const projectDir = options.projectDir === void 0 ? process7.cwd() : options.projectDir;
2883
+ const declared = projectDir === null || projectDir.trim().length === 0 ? null : projectDshEntry(projectDir);
2884
+ if (declared !== null && realPath(declared) !== realPath(launcherFor(stateDir, declared))) {
2885
+ const launcher = writeDshLauncher({
2886
+ stateDir,
2887
+ dshHome: dshHome2,
2888
+ resolvedEntry: declared,
2889
+ pinnedEntry: declared,
2890
+ repin: true,
2891
+ entryExtension: path10.extname(declared),
2892
+ searchRoots: [dshSearchRoot(declared), projectDir].filter((root) => typeof root === "string" && root.length > 0)
2893
+ });
2894
+ return { program: process7.execPath, args: [declared], cliEntry: declared, shimPath: null, source: "entry", launcherPath: launcher.path };
2895
+ }
2843
2896
  const local = typeof argv1 === "string" && argv1.length > 0 ? launchedEntry(argv1) : null;
2844
2897
  if (local !== null && realPath(local) !== realPath(dshLauncherPath(stateDir, path10.extname(local)))) {
2845
2898
  const launcher = writeDshLauncher({
@@ -2880,6 +2933,9 @@ async function resolveDshLaunch(options = {}) {
2880
2933
  const launch = resolveShimmedCli(found, process7.execPath);
2881
2934
  return launch === null ? null : { ...launch, launcherPath: null };
2882
2935
  }
2936
+ function launcherFor(stateDir, entry) {
2937
+ return dshLauncherPath(stateDir, path10.extname(entry));
2938
+ }
2883
2939
  function buildCommand(launch, launcherPath2) {
2884
2940
  if (launcherPath2 !== null && launcherPath2.length > 0)
2885
2941
  return { command: process7.execPath, entryArgs: [launcherPath2] };
@@ -2894,13 +2950,24 @@ function entryScript(config) {
2894
2950
  const rest = config.args ?? [];
2895
2951
  return rest.find((arg) => path10.isAbsolute(arg)) ?? program;
2896
2952
  }
2897
- function pinnedScriptFor(launch) {
2953
+ function localDshCommand(projectDir, entry) {
2954
+ if (typeof projectDir !== "string" || projectDir.trim().length === 0 || entry === null)
2955
+ return null;
2956
+ const shim = resolveShimmedCli(path10.join(projectDir, "node_modules", ".bin", "dsh"))?.cliEntry ?? null;
2957
+ if (shim === null)
2958
+ return null;
2959
+ return realPath(shim) === realPath(entry) ? "dsh" : null;
2960
+ }
2961
+ function pinnedScriptFor(launch, projectDir) {
2898
2962
  if (launch === null)
2899
2963
  return null;
2964
+ const entry = launch.cliEntry ?? launch.args.find((arg) => path10.isAbsolute(arg)) ?? null;
2965
+ if (localDshCommand(projectDir, entry) === "dsh")
2966
+ return "dsh";
2900
2967
  const launcher = launch.launcherPath;
2901
2968
  if (launcher !== null && launcher.length > 0)
2902
2969
  return launcher;
2903
- return launch.cliEntry ?? launch.args.find((arg) => path10.isAbsolute(arg)) ?? null;
2970
+ return entry;
2904
2971
  }
2905
2972
  function isBareCommand(stored) {
2906
2973
  return !/[/\\]/.test(stored) && (stored === "dsh" || /^dsh\.(?:cmd|exe)$/i.test(stored));
@@ -2915,23 +2982,24 @@ function needsLauncherRepair(config) {
2915
2982
  return true;
2916
2983
  return (config.args ?? []).some((arg) => path10.isAbsolute(arg));
2917
2984
  }
2918
- function launcherRepair(config, launch) {
2919
- const pinned = pinnedScriptFor(launch);
2985
+ function launcherRepair(config, launch, options = {}) {
2986
+ const pinned = pinnedScriptFor(launch, options.projectDir);
2920
2987
  if (pinned === null || launch === null)
2921
2988
  return null;
2989
+ const targetBare = pinned === "dsh";
2990
+ const storedBare = isBareCommand(entryScript(config) ?? "");
2922
2991
  const stored = entryScript(config);
2923
2992
  if (stored === null || stored === pinned)
2924
2993
  return null;
2925
- const bare = isBareCommand(stored);
2926
- if (!bare) {
2994
+ if (!storedBare && options.ownedCommand !== true) {
2927
2995
  const target = resolveShimmedCli(stored, process7.execPath)?.cliEntry ?? stored;
2928
- if (target !== pinned && target !== launch.cliEntry)
2996
+ if (target !== launch.cliEntry && target !== pinned)
2929
2997
  return null;
2930
2998
  }
2931
2999
  const args = config.args ?? [];
2932
- const inline = bare ? -1 : args.findIndex((arg) => path10.isAbsolute(arg));
2933
- const rest = inline === -1 ? args : args.filter((_, index) => index !== inline);
2934
- return { command: process7.execPath, args: [pinned, ...rest] };
3000
+ const script = storedBare ? -1 : args.findIndex((arg) => path10.isAbsolute(arg) || arg === pinned);
3001
+ const rest = script === -1 ? args : args.filter((_, index) => index !== script);
3002
+ return targetBare ? { command: "dsh", args: rest, ...typeof options.projectDir === "string" && options.projectDir.length > 0 ? { cwd: options.projectDir } : {} } : { command: process7.execPath, args: [pinned, ...rest] };
2935
3003
  }
2936
3004
  function buildDshEntry(facts) {
2937
3005
  const launch = facts.launch;
@@ -2949,7 +3017,9 @@ function buildDshEntry(facts) {
2949
3017
  );
2950
3018
  }
2951
3019
  const launcherArgs = profile === "web" ? ["web"] : ["--profile", profile];
2952
- const generated = launch === null ? { command: "dsh", entryArgs: [] } : buildCommand(launch, facts.launcherPath ?? null);
3020
+ const projectDir = facts.projectDir === void 0 || facts.projectDir === null ? process7.cwd() : facts.projectDir;
3021
+ const local = launch === null ? null : localDshCommand(projectDir, launch.cliEntry);
3022
+ const generated = launch === null || local === "dsh" ? { command: "dsh", entryArgs: [] } : buildCommand(launch, facts.launcherPath ?? null);
2953
3023
  return {
2954
3024
  id: facts.id,
2955
3025
  label: "DSH web",
@@ -2959,7 +3029,7 @@ function buildDshEntry(facts) {
2959
3029
  args: [...generated.entryArgs, ...launcherArgs, ...appArgs],
2960
3030
  bind: "local",
2961
3031
  port: facts.port,
2962
- cwd: process7.cwd(),
3032
+ cwd: projectDir,
2963
3033
  env: {},
2964
3034
  dataEnvs: { DSH_HOME: facts.dshHome },
2965
3035
  onPortConflict: "kill",
@@ -3190,6 +3260,11 @@ function ownedDrift(live, intent) {
3190
3260
  drift.push("stop.killPortHolders");
3191
3261
  return drift;
3192
3262
  }
3263
+ function isCreatedEntry(snapshot) {
3264
+ if (snapshot === void 0)
3265
+ return false;
3266
+ return Object.keys(snapshot).length === 1 && typeof snapshot.id === "string";
3267
+ }
3193
3268
  function snapshotOwned(live) {
3194
3269
  const killPortHolders = live.stop?.killPortHolders;
3195
3270
  return {
@@ -3231,7 +3306,7 @@ async function runCli(deps, command) {
3231
3306
  return await deps.exec(args, deps.env);
3232
3307
  const launch = deps.launch;
3233
3308
  if (launch === null)
3234
- return { code: null, stdout: "", stderr: "no home-hosted CLI is available" };
3309
+ return { code: null, stdout: "", stderr: "no home-hosted CLI is available", error: null };
3235
3310
  return await run(launch.program, args, {
3236
3311
  env: deps.env,
3237
3312
  timeoutMs: deps.timeoutMs ?? 9e4
@@ -3249,7 +3324,7 @@ async function startPanel(deps) {
3249
3324
  async function stopPanel(deps) {
3250
3325
  const result = await runCli(deps, "down");
3251
3326
  if (result.code !== 0) {
3252
- const detail = result.stderr.trim() || result.stdout.trim() || `the CLI exited ${String(result.code)}`;
3327
+ const detail = result.error ?? (result.stderr.trim() || result.stdout.trim() || `the CLI exited ${String(result.code)}`);
3253
3328
  return { ok: false, detail: `the panel did not stop: ${detail}` };
3254
3329
  }
3255
3330
  return { ok: true, detail: "the panel stopped; the servers it supervised stopped with it" };
@@ -3573,13 +3648,19 @@ var HomeHostedService = class extends Service {
3573
3648
  tokenDetail = "";
3574
3649
  /** When a missing managed entry was last put back, so a poll cannot become a write loop. */
3575
3650
  entryRecoveryAt = 0;
3651
+ /** When a managed row was last re-pointed at the pinned dsh, for the same reason. */
3652
+ commandRepairAt = 0;
3576
3653
  /** The last panel inventory, so a model-step assembly never reads the disk. */
3577
3654
  instanceCache = null;
3578
3655
  snapshotsFile;
3579
- async resolveDsh(dshHome2) {
3656
+ projectDir() {
3657
+ return this.options.projectDir ?? process10.cwd();
3658
+ }
3659
+ async resolveDsh(dshHome2, projectDir = this.projectDir()) {
3580
3660
  return await (this.options.resolveDsh ?? ((options) => resolveDshLaunch(options)))({
3581
3661
  dshHome: dshHome2,
3582
- stateDir: this.options.stateDir
3662
+ stateDir: this.options.stateDir,
3663
+ projectDir
3583
3664
  });
3584
3665
  }
3585
3666
  // -------------------------------------------------------------------------
@@ -4015,7 +4096,8 @@ var HomeHostedService = class extends Service {
4015
4096
  profile: detectProfile(process10.argv, process10.env),
4016
4097
  dshHome: harnessHome,
4017
4098
  launch: dsh,
4018
- launcherPath: dsh?.launcherPath ?? null
4099
+ launcherPath: dsh?.launcherPath ?? null,
4100
+ projectDir: this.projectDir()
4019
4101
  });
4020
4102
  return { ...generated, ...patch };
4021
4103
  }
@@ -4030,7 +4112,8 @@ var HomeHostedService = class extends Service {
4030
4112
  return null;
4031
4113
  const harnessHome = process10.env.DSH_HOME ?? dshHome();
4032
4114
  const dsh = await this.resolveDsh(harnessHome);
4033
- return launcherRepair(live, dsh);
4115
+ const owned = isCreatedEntry(this.snapshots()[live.id]);
4116
+ return launcherRepair(live, dsh, { ownedCommand: owned, projectDir: this.projectDir() });
4034
4117
  }
4035
4118
  async writeOwned(intent) {
4036
4119
  const live = (await this.liveEntries()).get(intent.id) ?? null;
@@ -4270,7 +4353,7 @@ var HomeHostedService = class extends Service {
4270
4353
  version: runtime?.version ?? null
4271
4354
  },
4272
4355
  launch,
4273
- { stateDir: this.options.stateDir }
4356
+ { stateDir: this.options.stateDir, unitName: bootUnitName(this.options.stateDir) }
4274
4357
  );
4275
4358
  if (launcher !== null) {
4276
4359
  const cliArgs2 = spec.args.slice(launch.args.length);
@@ -4310,6 +4393,8 @@ var HomeHostedService = class extends Service {
4310
4393
  if (spec === null)
4311
4394
  throw new HomeHostedError((await this.cli()).resolution.status.detail, "CLI_NOT_FOUND");
4312
4395
  const result = await this.ladder().install(spec, mechanism);
4396
+ if (result.ok)
4397
+ await this.retireLegacyBootEntry(spec, mechanism);
4313
4398
  const current = this.options.settings.get().autostart;
4314
4399
  this.options.settings.update({
4315
4400
  autostart: {
@@ -4334,6 +4419,8 @@ var HomeHostedService = class extends Service {
4334
4419
  if (spec === null)
4335
4420
  throw new HomeHostedError((await this.cli()).resolution.status.detail, "CLI_NOT_FOUND");
4336
4421
  const result = await this.ladder().uninstall(spec, mechanism);
4422
+ if (result.ok)
4423
+ await this.retireLegacyBootEntry(spec, mechanism);
4337
4424
  const current = this.options.settings.get().autostart;
4338
4425
  this.options.settings.update({
4339
4426
  autostart: {
@@ -4351,6 +4438,28 @@ var HomeHostedService = class extends Service {
4351
4438
  });
4352
4439
  return { result, status: await this.bootStatus(mechanism) };
4353
4440
  }
4441
+ /**
4442
+ * Remove the pre-per-instance artifact once this state root has its own.
4443
+ *
4444
+ * An install that used to write `home-hosted` now writes `home-hosted-<hash>`,
4445
+ * and a machine left with both would autostart the panel twice — or keep
4446
+ * starting it from an entry the page no longer reports. Only an artifact
4447
+ * carrying this plugin's marker is touched, so somebody else's unit survives.
4448
+ */
4449
+ async retireLegacyBootEntry(spec, mechanism) {
4450
+ if (spec.unitName === LEGACY_BOOT_UNIT_NAME)
4451
+ return;
4452
+ const legacy = { ...spec, unitName: LEGACY_BOOT_UNIT_NAME };
4453
+ try {
4454
+ const status = await this.ladder().status(legacy, mechanism);
4455
+ if (status.unitPath === null || status.state === "not-installed" || status.state === "unsupported")
4456
+ return;
4457
+ if (!fs15.existsSync(status.unitPath))
4458
+ return;
4459
+ await this.ladder().uninstall(legacy, status.mechanism ?? mechanism);
4460
+ } catch {
4461
+ }
4462
+ }
4354
4463
  // -------------------------------------------------------------------------
4355
4464
  // Panel lifecycle
4356
4465
  // -------------------------------------------------------------------------
@@ -4436,7 +4545,29 @@ var HomeHostedService = class extends Service {
4436
4545
  */
4437
4546
  async reconcile() {
4438
4547
  await this.ensureManagedEntry();
4548
+ await this.repairManagedCommand();
4439
4549
  await this.repairBootEntry();
4550
+ await this.migrateBootEntryName();
4551
+ }
4552
+ /**
4553
+ * An upgrade from a release that named every artifact `home-hosted` leaves
4554
+ * that one behind while this state root now writes its own name. Once our own
4555
+ * artifact is in place, the old one is retired — otherwise the machine
4556
+ * autostarts the panel twice, or from an entry the page no longer reports.
4557
+ */
4558
+ async migrateBootEntryName() {
4559
+ if (!this.options.settings.get().autostart.enabled)
4560
+ return;
4561
+ const spec = await this.bootSpec();
4562
+ if (spec === null || spec.unitName === LEGACY_BOOT_UNIT_NAME)
4563
+ return;
4564
+ try {
4565
+ const own = await this.bootStatus();
4566
+ if (own.state !== "enabled-running" && own.state !== "enabled-failing" && own.state !== "installed-disabled")
4567
+ return;
4568
+ await this.retireLegacyBootEntry(spec, own.mechanism ?? void 0);
4569
+ } catch {
4570
+ }
4440
4571
  }
4441
4572
  /**
4442
4573
  * Put back a managed entry that no longer exists, while its intent still wants
@@ -4463,6 +4594,34 @@ var HomeHostedService = class extends Service {
4463
4594
  } catch {
4464
4595
  }
4465
4596
  }
4597
+ /**
4598
+ * Re-point a managed `dsh` row that runs the wrong dsh.
4599
+ *
4600
+ * A row created before the launcher existed, or by an older release that took
4601
+ * whatever PATH answered, keeps starting that copy — and no click would fix
4602
+ * it, because the plugin only writes the row when the toggle is applied. So
4603
+ * the drift is repaired where the entry's existence is already checked, under
4604
+ * the same throttle, and only for a row this plugin owns.
4605
+ */
4606
+ async repairManagedCommand() {
4607
+ const id = this.options.defaultEntryId;
4608
+ if (!this.options.settings.get().manageDsh || !this.options.settings.intentFor(id).autostart)
4609
+ return;
4610
+ const now = Date.now();
4611
+ if (now - this.commandRepairAt < ENTRY_RECOVERY_INTERVAL_MS)
4612
+ return;
4613
+ const live = (await this.liveEntries()).get(id)?.config ?? null;
4614
+ if (live === null || this.snapshots()[id] === void 0 || !needsLauncherRepair(live))
4615
+ return;
4616
+ const repair = await this.dshCommandRepair(live);
4617
+ if (repair === null)
4618
+ return;
4619
+ this.commandRepairAt = now;
4620
+ try {
4621
+ await this.writeOwned({ ...this.options.settings.intentFor(id), autostart: true });
4622
+ } catch {
4623
+ }
4624
+ }
4466
4625
  /** Re-assert the boot entry when the OS still has it but it stopped working. */
4467
4626
  async repairBootEntry() {
4468
4627
  if (!this.options.settings.get().autostart.enabled)
@@ -4516,7 +4675,7 @@ var HomeHostedService = class extends Service {
4516
4675
  boot: await this.bootStatus(),
4517
4676
  // A deleted managed entry is put back here as well as at startup: whoever
4518
4677
  // deleted it is looking at the page that reports it missing.
4519
- entries: await this.ensureManagedEntry().then(async () => await this.entriesStatus()),
4678
+ entries: await this.ensureManagedEntry().then(async () => await this.repairManagedCommand()).then(async () => await this.entriesStatus()),
4520
4679
  servers,
4521
4680
  settings: this.options.settings.get(),
4522
4681
  cli,