strom-research 1.13.0-beta.2 → 1.13.0-beta.4

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.
@@ -275,6 +275,9 @@
275
275
  "ui.doc.app.seen": "nalezena v počítači",
276
276
  "ui.doc.app.no": "není chtěná",
277
277
  "ui.doc.app.unknown": "nenalezena",
278
+ "ui.doc.appurl": "adresa aplikace Strom",
279
+ "ui.doc.appurl.config": "nastavení strom.app.url „{value}“ není adresa aplikace Strom – strom app nic neotevře, dokud se neopraví nebo neodebere",
280
+ "ui.doc.appurl.env": "proměnná STROM_APP_URL „{value}“ není adresa aplikace Strom – strom app nic neotevře, dokud se neopraví nebo neodebere",
278
281
  "ui.doc.tree.ok": "{name}: data v pořádku a zapečetěná",
279
282
  "ui.doc.tree.bad": "{name}: problémů {n}",
280
283
  "ui.doc.allok": "Všechno potřebné je na místě.",
@@ -654,7 +657,7 @@
654
657
  "ui.help.archive.stats": "Archiv v kostce: osoby, jak jisté jsou údaje, předkové v každé generaci",
655
658
  "ui.help.archive.pack": "Zabalit archiv do jednoho souboru ZIP, aby v něm mohl pokračovat někdo jiný",
656
659
  "ui.help.archive.intake": "Přidat podklady od rodiny: dokumenty, fotky, rodokmen, co je známo",
657
- "ui.help.title": "strom <příkaz> – co člověk se stromem dělá (nebo jen {strom}: menu)",
660
+ "ui.help.title": "{strom} <příkaz> – co člověk se stromem dělá (nebo jen {strom}: menu)",
658
661
  "ui.help.more": "Nápověda k jednomu příkazu: strom help <příkaz> --human · všechny příkazy anglicky (co čte agent): strom help",
659
662
  "ui.help.more.archive": "Nápověda k jednomu příkazu: strom help <příkaz> --human",
660
663
  "ui.help.examples": "Příklady:",
@@ -1496,6 +1499,10 @@
1496
1499
  "ui.error.arg.missing": "chybí <{arg}>",
1497
1500
  "ui.error.arg.missing.hint": "strom help {cmd}",
1498
1501
  "ui.error.prefix": "chyba",
1502
+ "ui.error.config.app-url": "neplatné strom.app.url „{value}“: není to adresa aplikace Strom",
1503
+ "ui.error.config.app-url.hint": "aplikace Strom: {web}, nebo její kopie na tomto počítači http://127.0.0.1:<port>/",
1504
+ "ui.error.config.app-url.config.hint": "aplikace Strom: {web}, nebo její kopie na tomto počítači http://127.0.0.1:<port>/\nopravit: strom config set strom.app.url <adresa> – nebo odebrat: strom config unset strom.app.url",
1505
+ "ui.error.config.app-url.env.hint": "aplikace Strom: {web}, nebo její kopie na tomto počítači http://127.0.0.1:<port>/\nproměnnou STROM_APP_URL opravit, nebo odebrat",
1499
1506
  "ui.error.backup.failed": "Zálohu před přechodem z {from} na {to} se nepodařilo vytvořit: {detail}. Nic se nepřepíná: tento strom neotevře žádný rodokmen k zápisu, dokud záloha nebude hotová.",
1500
1507
  "ui.error.backup.failed.hint": "Uvolnit místo na disku ({where}) a spustit strom znovu – nebo se vrátit: strom update --channel {back}",
1501
1508
  "ui.error.backup.failed.update": "Zálohu před přechodem z {from} na {to} se nepodařilo vytvořit: {detail}. Nic se nenainstalovalo.",
@@ -1587,6 +1594,7 @@
1587
1594
  "ui.arg.tree-name": "název",
1588
1595
  "ui.arg.url": "adresa",
1589
1596
  "ui.arg.value": "hodnota",
1597
+ "ui.arg.address": "adresa",
1590
1598
  "ui.arg.what": "co",
1591
1599
  "ui.arg.where": "kde",
1592
1600
  "ui.arg.who": "kdo",
@@ -1614,7 +1622,7 @@
1614
1622
  "ui.error.event.needs-reason.hint": "např. --reason \"záznam S0012 uvádí 1811, ne 1813\"",
1615
1623
  "ui.error.family.needs-reason": "změna toho, kdo do rodiny patří nebo jak, potřebuje --reason",
1616
1624
  "ui.error.family.needs-reason.hint": "např. --reason \"křest uvádí Jana jako otčíma\"",
1617
- "ui.error.person.needs-reason": "změna jména nebo známého pohlaví potřebuje --reason",
1625
+ "ui.error.person.needs-reason": "změna jména, jeho titulu nebo známého pohlaví potřebuje --reason",
1618
1626
  "ui.error.person.needs-reason.hint": "např. --reason \"špatně přečteno: matrika má Novák, ne Nowak\"",
1619
1627
  "ui.error.retract.needs-reason": "stažení potřebuje --reason",
1620
1628
  "ui.error.retract.needs-reason.hint": "např. --reason \"špatně přečteno: zápis uvádí Annu, ne Antona\"",
@@ -275,6 +275,9 @@
275
275
  "ui.doc.app.seen": "auf dem Computer gefunden",
276
276
  "ui.doc.app.no": "nicht gewünscht",
277
277
  "ui.doc.app.unknown": "nicht gefunden",
278
+ "ui.doc.appurl": "Adresse der Strom-App",
279
+ "ui.doc.appurl.config": "die Einstellung strom.app.url „{value}“ ist keine Adresse der Strom-App – strom app öffnet nichts, bis sie berichtigt oder entfernt ist",
280
+ "ui.doc.appurl.env": "die Variable STROM_APP_URL „{value}“ ist keine Adresse der Strom-App – strom app öffnet nichts, bis sie berichtigt oder entfernt ist",
278
281
  "ui.doc.tree.ok": "{name}: Daten in Ordnung und versiegelt",
279
282
  "ui.doc.tree.bad": "{name}: Probleme: {n}",
280
283
  "ui.doc.allok": "Alles Nötige ist vorhanden.",
@@ -654,7 +657,7 @@
654
657
  "ui.help.archive.stats": "Das Archiv auf einen Blick: Personen, wie sicher die Angaben sind, Vorfahren je Generation",
655
658
  "ui.help.archive.pack": "Das Archiv in eine ZIP-Datei packen, damit jemand anderes weitermachen kann",
656
659
  "ui.help.archive.intake": "Material der Familie hinzufügen: Dokumente, Fotos, einen Stammbaum, eigenes Wissen",
657
- "ui.help.title": "strom <Befehl> – was ein Mensch mit strom tut (oder nur {strom}: das Menü)",
660
+ "ui.help.title": "{strom} <Befehl> – was ein Mensch mit strom tut (oder nur {strom}: das Menü)",
658
661
  "ui.help.more": "Hilfe zu einem Befehl: strom help <Befehl> --human · alle Befehle auf Englisch (was ein Agent liest): strom help",
659
662
  "ui.help.more.archive": "Hilfe zu einem Befehl: strom help <Befehl> --human",
660
663
  "ui.help.examples": "Beispiele:",
@@ -1496,6 +1499,10 @@
1496
1499
  "ui.error.arg.missing": "<{arg}> fehlt",
1497
1500
  "ui.error.arg.missing.hint": "strom help {cmd}",
1498
1501
  "ui.error.prefix": "Fehler",
1502
+ "ui.error.config.app-url": "ungültige strom.app.url „{value}“: keine Adresse der Strom-App",
1503
+ "ui.error.config.app-url.hint": "die Strom-App: {web}, oder eine Kopie auf diesem Computer http://127.0.0.1:<port>/",
1504
+ "ui.error.config.app-url.config.hint": "die Strom-App: {web}, oder eine Kopie auf diesem Computer http://127.0.0.1:<port>/\nberichtigen: strom config set strom.app.url <Adresse> – oder entfernen: strom config unset strom.app.url",
1505
+ "ui.error.config.app-url.env.hint": "die Strom-App: {web}, oder eine Kopie auf diesem Computer http://127.0.0.1:<port>/\ndie Variable STROM_APP_URL berichtigen oder entfernen",
1499
1506
  "ui.error.backup.failed": "Die Sicherung vor dem Wechsel von {from} auf {to} ließ sich nicht erstellen: {detail}. Es wird nichts umgestellt: dieses strom öffnet keinen Stammbaum zum Schreiben, bis die Sicherung erstellt ist.",
1500
1507
  "ui.error.backup.failed.hint": "Platz auf dem Datenträger freimachen ({where}) und strom erneut starten – oder zurück: strom update --channel {back}",
1501
1508
  "ui.error.backup.failed.update": "Die Sicherung vor dem Wechsel von {from} auf {to} ließ sich nicht erstellen: {detail}. Es wurde nichts installiert.",
@@ -1587,6 +1594,7 @@
1587
1594
  "ui.arg.tree-name": "Name",
1588
1595
  "ui.arg.url": "Adresse",
1589
1596
  "ui.arg.value": "Wert",
1597
+ "ui.arg.address": "Adresse",
1590
1598
  "ui.arg.what": "was",
1591
1599
  "ui.arg.where": "wo",
1592
1600
  "ui.arg.who": "wer",
@@ -1614,7 +1622,7 @@
1614
1622
  "ui.error.event.needs-reason.hint": "z. B. --reason \"Eintrag S0012 nennt 1811, nicht 1813\"",
1615
1623
  "ui.error.family.needs-reason": "wer zur Familie gehört oder wie, ändert sich nur mit --reason",
1616
1624
  "ui.error.family.needs-reason.hint": "z. B. --reason \"die Taufe nennt Jan als Stiefvater\"",
1617
- "ui.error.person.needs-reason": "ein Name oder ein bekanntes Geschlecht ändert sich nur mit --reason",
1625
+ "ui.error.person.needs-reason": "ein Name, sein Titel oder ein bekanntes Geschlecht ändert sich nur mit --reason",
1618
1626
  "ui.error.person.needs-reason.hint": "z. B. --reason \"falsch gelesen: im Register steht Novák, nicht Nowak\"",
1619
1627
  "ui.error.retract.needs-reason": "Zurückziehen braucht --reason",
1620
1628
  "ui.error.retract.needs-reason.hint": "z. B. --reason \"falsch gelesen: der Eintrag nennt Anna, nicht Anton\"",
package/dist/cli/help.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // Help text generated from the registry. Short by design: examples first,
2
2
  // then arguments and options.
3
- import { GROUPS, GLOBAL_OPTIONS, WRITE_OPTIONS, commands, match, subcommandsOf, usageLine } from "./registry.js";
3
+ import { GROUPS, GLOBAL_OPTIONS, WRITE_OPTIONS, commands, descriptionOf, match, subcommandsOf, usageLine } from "./registry.js";
4
4
  import { table } from "./format.js";
5
5
  import { suggest } from "./execute.js";
6
6
  import { UI, ui } from "./ui.js";
@@ -43,13 +43,14 @@ function inArchive(def) {
43
43
  return own;
44
44
  return AI_WORDS.test(def.summary) ? undefined : def.summary;
45
45
  }
46
- export function commandHelp(def, archive = false) {
46
+ export function commandHelp(def, archive, appUrl) {
47
47
  const summary = archive ? inArchive(def) : def.summary;
48
48
  if (summary === undefined)
49
49
  return `strom ${def.path.join(" ")}: not in an archive (research is switched on by the user: strom mode research)`;
50
50
  const out = [usageLine(def), "", summary];
51
- if (def.description && !(archive && AI_WORDS.test(def.description)))
52
- out.push("", def.description);
51
+ const description = descriptionOf(def, appUrl);
52
+ if (description && !(archive && AI_WORDS.test(description)))
53
+ out.push("", description);
53
54
  const examples = (def.examples ?? []).filter((e) => !(archive && AI_WORDS.test(e)));
54
55
  if (examples.length)
55
56
  out.push("", "Examples:", ...examples.map((e) => ` ${e}`));
@@ -111,14 +112,14 @@ export function overview(archive = false) {
111
112
  out.push("", "One command — usage, examples, options: strom help <command>", archive ? "Each command with what it does: strom commands <group>" : "Each command with what it does: strom commands [<group>] · with options and examples: strom commands <group> --json");
112
113
  return out.join("\n");
113
114
  }
114
- export function helpFor(input, archive = false) {
115
+ export function helpFor(input, archive, appUrl) {
115
116
  // "strom help 'research new'" — one quoted argument with several words.
116
117
  const words = input.flatMap((w) => w.trim().split(/\s+/)).filter(Boolean);
117
118
  if (words.length === 0)
118
119
  return overview(archive);
119
120
  const found = match(words);
120
121
  if (found && found.used === words.length)
121
- return commandHelp(found.def, archive);
122
+ return commandHelp(found.def, archive, appUrl);
122
123
  if (subcommandsOf(words).length > 0)
123
124
  return groupHelp(words, archive);
124
125
  const all = commands().filter((c) => c.path.length > 0).map((c) => c.path.join(" "));
@@ -185,7 +186,7 @@ function pointer(words, as) {
185
186
  export function helpAs(input, as) {
186
187
  const words = input.flatMap((w) => w.trim().split(/\s+/)).filter(Boolean);
187
188
  if (!as.human)
188
- return helpFor(words, as.archive) + pointer(words, as);
189
+ return helpFor(words, as.archive, as.appUrl) + pointer(words, as);
189
190
  if (words.length === 0)
190
191
  return humanOverview(as);
191
192
  const found = match(words);
package/dist/cli/main.js CHANGED
@@ -16,7 +16,7 @@ import { fireHooks, HOOK_INTERFACE } from "../core/hooks.js";
16
16
  import { prependPath } from "../runners/runner.js";
17
17
  import { shimDir } from "../commands/session.js";
18
18
  import { isAgent } from "../core/which.js";
19
- import { noticeStromApp } from "../core/stromapp.js";
19
+ import { appUrlShown, noticeStromApp } from "../core/stromapp.js";
20
20
  import { lastChannelOf, pendingTransition, updateChannel } from "../core/update.js";
21
21
  import { backupBefore, backupSaid } from "./backups.js";
22
22
  import { installation } from "../core/self.js";
@@ -157,7 +157,7 @@ export async function main(argv, io, env, cwd) {
157
157
  catch (err) {
158
158
  if (err instanceof GroupOnly) {
159
159
  const c = Context.fromOptions({ env, cwd, io, json, values: {} });
160
- io.stdout(groupHelpAs(err.group, { archive: c.archiveHere(), human: false, lang: c.uiLang(), pointer: io.tty && !isAgent(env) }) + "\n");
160
+ io.stdout(groupHelpAs(err.group, { archive: c.archiveHere(), human: false, lang: c.uiLang(), appUrl: appUrlShown(c.settings), pointer: io.tty && !isAgent(env) }) + "\n");
161
161
  return EXIT.ok;
162
162
  }
163
163
  throw err;
@@ -170,7 +170,7 @@ export async function main(argv, io, env, cwd) {
170
170
  // the agent's help, whoever asks (Milan, 2026-10-04: "výchozí je pro agenta"); a person at a terminal is told in a
171
171
  // line of their language how to get theirs (strom help <command> --human)
172
172
  const c = Context.fromOptions({ env, cwd, io, json, values: v });
173
- io.stdout(helpAs(def.path, { archive: c.archiveHere(), human: false, lang: c.uiLang(), pointer: io.tty && !isAgent(env) }) + "\n");
173
+ io.stdout(helpAs(def.path, { archive: c.archiveHere(), human: false, lang: c.uiLang(), appUrl: appUrlShown(c.settings), pointer: io.tty && !isAgent(env) }) + "\n");
174
174
  return EXIT.ok;
175
175
  }
176
176
  if (v.version) {
@@ -20,7 +20,7 @@ import { appWindow } from "../core/appbrowser.js";
20
20
  import { moveTree } from "./move.js";
21
21
  import { changeLines } from "../core/changelog.js";
22
22
  import { startLive } from "../core/live.js";
23
- import { adoptAppUrl, appTreeNameFromInstall, defaultAppUrl } from "../core/stromapp.js";
23
+ import { adoptAppUrl, appTreeNameFromInstall, defaultAppUrl, stromAppUrl } from "../core/stromapp.js";
24
24
  import { stromReadsTags } from "../gedcom/export.js";
25
25
  import { appMarkFromInstall } from "../core/links.js";
26
26
  import { settleFromApp } from "./wizard.js";
@@ -288,6 +288,9 @@ export async function newFromApp(ctx, runIn, token, openAt, forward, opts = {})
288
288
  ctx.settings.config.stromAppUrl = move.mark.app;
289
289
  ctx.settings.save();
290
290
  }
291
+ // a strom.app.url that is no address of the app: refused before anything is asked or made — the tree's way back
292
+ // would be an address built on it with the bridge's secret (B1-c)
293
+ stromAppUrl(ctx.settings);
291
294
  let lang = ctx.uiLang();
292
295
  let t = translator(lang);
293
296
  out(ctx, t("ui.link.new.title"));
package/dist/cli/menu.js CHANGED
@@ -24,7 +24,7 @@ import { lacksImages } from "../core/queue.js";
24
24
  import { othersAtWork } from "../core/session.js";
25
25
  import { DEFAULT_RUN_MINUTES } from "../core/config.js";
26
26
  import { truncate } from "./format.js";
27
- import { installedStromApp, noticeStromApp, stromAppState, stromAppUrl } from "../core/stromapp.js";
27
+ import { appUrlSetting, installedStromApp, noticeStromApp, stromAppState } from "../core/stromapp.js";
28
28
  import { appBrowsers } from "../core/chromium.js";
29
29
  import { liveWorkers, runsAtWork } from "../core/workers.js";
30
30
  import { askStromApp, settleInstall, shortcutName } from "./wizard.js";
@@ -344,7 +344,7 @@ export async function runMenu(ctx, run) {
344
344
  const app = stromAppState(ctx.settings);
345
345
  if (app !== "no") {
346
346
  const atWork = liveWorkers(root).length > 0 || runsAtWork(root).length > 0;
347
- const hasApp = app !== "unknown" || Boolean(installedStromApp(ctx.env, process.platform, stromAppUrl(ctx.settings)));
347
+ const hasApp = app !== "unknown" || Boolean(installedStromApp(ctx.env, process.platform, appUrlSetting(ctx.settings).url));
348
348
  appItem = {
349
349
  key: "7",
350
350
  label: t(!hasApp ? "ui.menu.app.new" : atWork ? "ui.menu.app.watch" : "ui.menu.app"),
@@ -500,7 +500,7 @@ async function openStromApp(ctx, run, lang, said) {
500
500
  await run(["app"]);
501
501
  return;
502
502
  }
503
- if (!installedStromApp(ctx.env, process.platform, stromAppUrl(ctx.settings)) && (await ctx.confirm(t("ui.app.notinstalled"), true))) {
503
+ if (!installedStromApp(ctx.env, process.platform, appUrlSetting(ctx.settings).url) && (await ctx.confirm(t("ui.app.notinstalled"), true))) {
504
504
  await run(["app", "install"]);
505
505
  return;
506
506
  }
@@ -73,11 +73,28 @@ export function usageLine(def) {
73
73
  });
74
74
  return ["strom", ...def.path, ...args, (def.options?.some((o) => !o.hidden) || def.writes) ? "[options]" : ""].filter(Boolean).join(" ");
75
75
  }
76
+ /**
77
+ * A command's description as it is said here: {appUrl} the copy of the Strom app `strom app` opens (stromAppUrl — the
78
+ * beta for a strom of the beta channel, strom.app.url where it says another; found: help app named stromapp.info on the
79
+ * beta).
80
+ */
81
+ export function descriptionOf(def, appUrl) {
82
+ const said = def.description?.replace(/\{appUrl\}/g, appUrl.url);
83
+ // an address of no Strom app in the setting: the help names the default and says so (B1-b)
84
+ return said && appUrl.invalid && def.description.includes("{appUrl}") ? `${said}\n${APP_URL_INVALID}` : said;
85
+ }
86
+ /** What a help says under the address of the Strom app when strom.app.url says no address of it (B1-b). */
87
+ export const APP_URL_INVALID = "Note: the setting strom.app.url (or STROM_APP_URL) is no address of the Strom app, so the address above is the default.\n" +
88
+ "Set it right (strom config set strom.app.url <address>) or remove it (strom config unset strom.app.url).";
89
+ /** What strom config where / config get say under a strom.app.url that is no address of the Strom app (B1-e). */
90
+ export const APP_URL_INVALID_SETTING = "Note: the setting strom.app.url (or STROM_APP_URL) is no address of the Strom app: strom app refuses it, and where only an address is named, the default is.\n" +
91
+ "Set it right (strom config set strom.app.url <address>) or remove it (strom config unset strom.app.url).";
76
92
  /** Catalog entry for `strom commands --json`: only what is there (empty fields are left out). */
77
- export function describe(def) {
93
+ export function describe(def, appUrl) {
78
94
  const out = { command: def.path.join(" "), usage: usageLine(def), summary: def.summary, group: def.group };
79
- if (def.description)
80
- out.description = def.description;
95
+ const description = descriptionOf(def, appUrl);
96
+ if (description)
97
+ out.description = description;
81
98
  if (def.writes)
82
99
  out.writes = true;
83
100
  if (def.tree)
package/dist/cli/ui.js CHANGED
@@ -242,6 +242,9 @@ export const UI = {
242
242
  "ui.doc.app.seen": "found on this computer",
243
243
  "ui.doc.app.no": "not wanted",
244
244
  "ui.doc.app.unknown": "not found",
245
+ "ui.doc.appurl": "address of the Strom app",
246
+ "ui.doc.appurl.config": "the setting strom.app.url \"{value}\" is no address of the Strom app — strom app opens nothing until it is set right or removed",
247
+ "ui.doc.appurl.env": "the variable STROM_APP_URL \"{value}\" is no address of the Strom app — strom app opens nothing until it is set right or removed",
245
248
  "ui.doc.tree.ok": "{name}: data consistent and sealed",
246
249
  "ui.doc.tree.bad": "{name}: {n} problem(s)",
247
250
  "ui.doc.allok": "Everything needed is in place.",
@@ -561,7 +564,7 @@ export const UI = {
561
564
  "ui.help.archive.stats": "The archive at a glance: people, how sure the facts are, ancestors in each generation",
562
565
  "ui.help.archive.pack": "Pack the archive into one ZIP file for someone to go on with it",
563
566
  "ui.help.archive.intake": "Add material from the family: documents, photos, a family tree, what the family knows",
564
- "ui.help.title": "strom <command> — what a person does with strom (or just {strom}: the menu)",
567
+ "ui.help.title": "{strom} <command> — what a person does with strom (or just {strom}: the menu)",
565
568
  "ui.help.more": "Help for one command: strom help <command> --human · every command in English (what an agent reads): strom help",
566
569
  "ui.help.more.archive": "Help for one command: strom help <command> --human",
567
570
  "ui.help.examples": "Examples:",
@@ -1346,6 +1349,10 @@ export const UI = {
1346
1349
  "ui.error.arg.missing": "<{arg}> is missing",
1347
1350
  "ui.error.arg.missing.hint": "strom help {cmd}",
1348
1351
  "ui.error.prefix": "error",
1352
+ "ui.error.config.app-url": "invalid strom.app.url \"{value}\": no address of the Strom app",
1353
+ "ui.error.config.app-url.hint": "the Strom app: {web}, or a copy on this computer http://127.0.0.1:<port>/",
1354
+ "ui.error.config.app-url.config.hint": "the Strom app: {web}, or a copy on this computer http://127.0.0.1:<port>/\nset it right: strom config set strom.app.url <address> — or remove it: strom config unset strom.app.url",
1355
+ "ui.error.config.app-url.env.hint": "the Strom app: {web}, or a copy on this computer http://127.0.0.1:<port>/\nthe variable STROM_APP_URL set right, or removed",
1349
1356
  "ui.error.backup.failed": "The backup before going from {from} to {to} could not be made: {detail}. Nothing is switched: this strom opens no family tree for writing until the backup is made.",
1350
1357
  "ui.error.backup.failed.hint": "Free room on the disk ({where}), then run strom again — or go back: strom update --channel {back}",
1351
1358
  "ui.error.backup.failed.update": "The backup before going from {from} to {to} could not be made: {detail}. Nothing was installed.",
@@ -1421,6 +1428,7 @@ export const UI = {
1421
1428
  "ui.arg.tree-name": "tree name",
1422
1429
  "ui.arg.url": "url",
1423
1430
  "ui.arg.value": "value",
1431
+ "ui.arg.address": "address",
1424
1432
  "ui.arg.what": "what",
1425
1433
  "ui.arg.where": "where",
1426
1434
  "ui.arg.who": "who",
@@ -1449,7 +1457,7 @@ export const UI = {
1449
1457
  "ui.error.event.needs-reason.hint": "e.g. --reason \"record S0012 gives 1811, not 1813\"",
1450
1458
  "ui.error.family.needs-reason": "changing who belongs to a family, or how, needs --reason",
1451
1459
  "ui.error.family.needs-reason.hint": "e.g. --reason \"the baptism names Jan as stepfather\"",
1452
- "ui.error.person.needs-reason": "changing a name or a known sex needs --reason",
1460
+ "ui.error.person.needs-reason": "changing a name, its title or a known sex needs --reason",
1453
1461
  "ui.error.person.needs-reason.hint": "e.g. --reason \"misread: the register has Novák, not Nowak\"",
1454
1462
  "ui.error.retract.needs-reason": "retracting needs --reason",
1455
1463
  "ui.error.retract.needs-reason.hint": "e.g. --reason \"misread: the entry names Anna, not Anton\"",
@@ -14,7 +14,7 @@ import { chooseWay, wayName } from "./ways.js";
14
14
  import { PROFILES } from "../agents/profiles.js";
15
15
  import { writeStored, PERMISSION_LEVELS } from "../core/config.js";
16
16
  import { globalTargets, installGlobal } from "../agents/global.js";
17
- import { appCopyOfInstall, appOpensLinks, defaultAppUrl, noticeStromApp, researchUrl, STROM_APP_URL, stromAppState, stromAppUrl } from "../core/stromapp.js";
17
+ import { appCopyOfInstall, appOpensLinks, defaultAppUrl, noticeStromApp, researchUrl, STROM_APP_URL, stromAppState, appUrlSetting } from "../core/stromapp.js";
18
18
  import { openForUser } from "../core/open.js";
19
19
  import { createShortcut } from "../core/shortcut.js";
20
20
  import { appMarkFromInstall, linkFiles, linkHandlerState, linkOwner, registerLinks } from "../core/links.js";
@@ -355,8 +355,12 @@ export async function askStromApp(ctx, lang, back = ui(lang, "ui.browse.back"))
355
355
  ctx.settings.config.stromApp = answer === "no" ? "no" : "yes";
356
356
  ctx.settings.save();
357
357
  if (answer === "install") {
358
- const url = stromAppUrl(ctx.settings);
359
- ctx.io.stdout(ui(lang, openForUser(url, ctx.env) ? "ui.app.install" : "ui.app.url", { url }) + "\n");
358
+ // a strom.app.url that is no address of the app: nothing opened, said how it stands (strom doctor says how to put it right)
359
+ const said = appUrlSetting(ctx.settings);
360
+ if (said.invalid)
361
+ ctx.io.stdout(ui(lang, said.invalid.source === "env" ? "ui.doc.appurl.env" : "ui.doc.appurl.config", { value: said.invalid.value }) + "\n");
362
+ else
363
+ ctx.io.stdout(ui(lang, openForUser(said.url, ctx.env) ? "ui.app.install" : "ui.app.url", { url: said.url }) + "\n");
360
364
  }
361
365
  return answer;
362
366
  }
@@ -577,6 +577,9 @@ function haystack(r) {
577
577
  Object.values(v).forEach(add);
578
578
  };
579
579
  const { id: _i, type: _t, created: _c, updated: _u, ...rest } = r;
580
+ // a person's titles ("Ing.", "ml.") are never searched as their name — the Strom app finds nobody by them either
581
+ if (r.type === "person")
582
+ rest.names = r.names.map(({ prefix: _p, suffix: _s, ...n }) => n);
580
583
  add(rest);
581
584
  return parts.join(" \u0001 ");
582
585
  }
@@ -5,7 +5,7 @@ import fs from "node:fs";
5
5
  import path from "node:path";
6
6
  import { newerVersion } from "../core/update.js";
7
7
  import { mb, tidyPlan, TIDY_SAID } from "../core/tidy.js";
8
- import { appOpensResearch, installedStromApp, stromAppUrl } from "../core/stromapp.js";
8
+ import { appOpensResearch, appUrlSetting, appUrlShown, installedStromApp } from "../core/stromapp.js";
9
9
  import { register, commands, describe, GROUPS } from "../cli/registry.js";
10
10
  import { UsageError } from "../core/errors.js";
11
11
  import { helpAs } from "../cli/help.js";
@@ -88,7 +88,7 @@ function orientation(ctx) {
88
88
  // What a conversation tells the user once, not again after it starts afresh (a new conversation, /clear): the Strom app, the stories, asking here.
89
89
  const told = ctx.settings.config.told ?? {};
90
90
  if (fs.existsSync(file))
91
- base.results = { file, app: !wanted ? "not-wanted" : installedStromApp(ctx.env, process.platform, stromAppUrl(ctx.settings)) ? "installed" : "offer", ...(told.app ? { told: true } : {}) };
91
+ base.results = { file, app: !wanted ? "not-wanted" : installedStromApp(ctx.env, process.platform, appUrlSetting(ctx.settings).url) ? "installed" : "offer", ...(told.app ? { told: true } : {}) };
92
92
  const vision = ctx.settings.models(ctx.settings.agent(tree.config).value, tree.config).vision;
93
93
  if (vision) {
94
94
  const other = readByOtherModels(tree, vision);
@@ -185,7 +185,7 @@ register({
185
185
  ? appOpensResearch(ctx.settings) && !o.results.told ? "ui.o.results.installed.live" : "ui.o.results.installed"
186
186
  : o.results.app === "offer"
187
187
  ? o.results.told ? "ui.o.results.offered" : appOpensResearch(ctx.settings) ? "ui.o.results.offer.live" : "ui.o.results.offer"
188
- : "ui.o.results.plain", { file: ctx.display(o.results.file), url: stromAppUrl(ctx.settings) }))
188
+ : "ui.o.results.plain", { file: ctx.display(o.results.file), url: appUrlSetting(ctx.settings).url }))
189
189
  : undefined, archive ? row(t("ui.o.mode"), t("ui.o.mode.archive")) : undefined, o.stories && !archive ? row(t("ui.o.stories"), t(`ui.o.stories.${o.stories}`)) : undefined, o.ask && !archive ? row(t("ui.o.ask"), t("ui.o.ask.tell")) : undefined, o.reread && !archive ? row(t("ui.o.reread"), t("ui.o.reread.offer", { n: o.reread.facts, models: o.reread.models.join(", "), model: o.reread.model })) : undefined, o.researches?.length
190
190
  ? t("ui.o.research") + "\n" + table(o.researches.map((r) => [` ${r.id}`, r.name, `[${r.state}]`, r.focus]))
191
191
  : t("ui.o.research.none"), o.working?.length
@@ -218,7 +218,7 @@ register({
218
218
  group: "start",
219
219
  args: [{ name: "filter", description: "only commands starting with these words, or of this group", variadic: true }],
220
220
  examples: ["strom commands", "strom commands source --json", "strom commands analysis"],
221
- run(_ctx, { args }) {
221
+ run(ctx, { args }) {
222
222
  const words = args.flatMap((a) => a.split(/\s+/)).filter(Boolean);
223
223
  const prefix = words.join(" ");
224
224
  const defs = commands().filter((c) => c.path.length > 0 && (!prefix || c.group === prefix || c.path.join(" ") === prefix || c.path.join(" ").startsWith(`${prefix} `)));
@@ -231,7 +231,8 @@ register({
231
231
  })
232
232
  .filter(Boolean)
233
233
  .join("\n\n");
234
- return { text, data: { version: VERSION, commands: defs.map(describe) } };
234
+ const appUrl = appUrlShown(ctx.settings);
235
+ return { text, data: { version: VERSION, commands: defs.map((d) => describe(d, appUrl)), ...(appUrl.invalid ? { invalid: ["strom.app.url"] } : {}) } };
235
236
  },
236
237
  }, {
237
238
  path: ["help"],
@@ -247,7 +248,7 @@ register({
247
248
  // the agent's help, whoever asks — a person's only with --human (Milan, 2026-10-04); a person at a terminal is told
248
249
  // how in a line of their language
249
250
  const human = Boolean(input.opts.human) && !input.opts.agent;
250
- const text = helpAs(input.args, { archive: ctx.archiveHere(), human, lang: ctx.uiLang(), pointer: !human && ctx.io.tty && !isAgent(ctx.env) });
251
+ const text = helpAs(input.args, { archive: ctx.archiveHere(), human, lang: ctx.uiLang(), appUrl: appUrlShown(ctx.settings), pointer: !human && ctx.io.tty && !isAgent(ctx.env) });
251
252
  return { text, data: { help: text } };
252
253
  },
253
254
  });
@@ -3,7 +3,7 @@ import { gitSize, lastCompacted } from "../core/history.js";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import { spawnSync } from "node:child_process";
6
- import { register } from "../cli/registry.js";
6
+ import { APP_URL_INVALID_SETTING, register } from "../cli/registry.js";
7
7
  import { lines, table } from "../cli/format.js";
8
8
  import { checkValue, configFile, DEFAULT_BUDGET, DEFAULT_RUN_MINUTES, OTHER_ENV, SETTINGS, settingDef, writeStored } from "../core/config.js";
9
9
  import { syncAgentFiles } from "../agents/files.js";
@@ -27,7 +27,7 @@ import { configDir, defaultHome, desktopDir, isolated, noLinks, ownCommand } fro
27
27
  import { backupBefore, backupSaid, lastBackupLine } from "../cli/backups.js";
28
28
  import { BACKUP_SAID_DAYS, lastBackup } from "../core/backup.js";
29
29
  import { planMove, sameFolder } from "../core/relocate.js";
30
- import { appOpensLinks, defaultAppUrl, researchUrl, stromAppUrl, stromAppState } from "../core/stromapp.js";
30
+ import { appOpensLinks, appUrlSetting, researchUrl, stromAppUrl, stromAppState } from "../core/stromapp.js";
31
31
  import { isInstalled } from "../agents/global.js";
32
32
  import { offerLinks, shortcutName } from "../cli/wizard.js";
33
33
  import { linkHandlerState, linkScheme } from "../core/links.js";
@@ -357,6 +357,11 @@ function diagnose(ctx) {
357
357
  add("app", "ok", t("ui.doc.app.isolated"));
358
358
  else
359
359
  add("app", "ok", t(`ui.doc.app.${app}`), app === "unknown" ? FIX : undefined, app === "unknown" ? "app" : undefined);
360
+ // the copy of the app strom opens: a setting that says no address of it (STROM_APP_URL, or written by hand into the
361
+ // settings) is a problem said with its way out — doctor never fails on it (strom app refuses to open it)
362
+ const appUrl = appUrlSetting(ctx.settings).invalid;
363
+ if (appUrl)
364
+ add("appurl", "fail", t(appUrl.source === "env" ? "ui.doc.appurl.env" : "ui.doc.appurl.config", { value: appUrl.value }), appUrl.source === "env" ? undefined : placeholders(lang, "strom config set strom.app.url <address> · strom config unset strom.app.url"));
360
365
  // …and whether it may start the research here (strom-research:// links): only while the app is wanted
361
366
  if (app !== "no" && appOpensLinks(ctx.settings)) {
362
367
  const links = linkHandlerState(ctx.env);
@@ -404,7 +409,8 @@ async function repair(ctx, checks, out) {
404
409
  }
405
410
  if (todo.has("links") && person)
406
411
  await offerLinks(ctx, lang);
407
- if (todo.has("app") && person && (await ctx.confirm(ui(lang, "ui.fix.app"), false))) {
412
+ // (never into the address of an invalid strom.app.url: its check says how to put it right)
413
+ if (todo.has("app") && person && !appUrlSetting(ctx.settings).invalid && (await ctx.confirm(ui(lang, "ui.fix.app"), false))) {
408
414
  openForUser(stromAppUrl(ctx.settings), ctx.env);
409
415
  out(ui(lang, "ui.app.install"));
410
416
  }
@@ -607,6 +613,15 @@ function treeSettings(ctx) {
607
613
  /** Effective value of a setting, with the default filled in. */
608
614
  function effective(ctx, def) {
609
615
  const s = ctx.settings;
616
+ // the address of the Strom app: read as everything reads it (appUrlSetting) — never failing; one that is no address of
617
+ // the app is shown as found, marked invalid (B1-e)
618
+ if (def.key === "strom.app.url") {
619
+ const said = appUrlSetting(s);
620
+ if (said.invalid)
621
+ return { value: said.invalid.value, source: said.invalid.source, invalid: true };
622
+ const r = s.resolve(def.key);
623
+ return r ?? { value: said.url, source: "default" };
624
+ }
610
625
  const tree = treeSettings(ctx);
611
626
  if (def.key === "home")
612
627
  return s.home() ?? { value: undefined, source: "unset" };
@@ -631,8 +646,6 @@ function effective(ctx, def) {
631
646
  return { value: DEFAULT_RUN_MINUTES, source: "default" };
632
647
  if (def.key === "connectors.consent")
633
648
  return { value: "off", source: "default" };
634
- if (def.key === "strom.app.url")
635
- return { value: defaultAppUrl(ctx.env), source: "default" };
636
649
  if (def.key === "browser.downloads")
637
650
  return { value: downloadsDir(ctx.env), source: "detected" };
638
651
  if (def.key === "agent.permissions")
@@ -749,10 +762,11 @@ register({
749
762
  run(ctx) {
750
763
  const rows = SETTINGS.map((def) => {
751
764
  const r = effective(ctx, def);
752
- return { key: def.key, value: r.value, source: r.source, env: def.env, tree: def.tree, description: def.description };
765
+ return { key: def.key, value: r.value, source: r.source, env: def.env, tree: def.tree, description: def.description, ...(r.invalid ? { invalid: true } : {}) };
753
766
  });
754
- const text = lines(table(rows.map((r) => [r.key, display(ctx, settingDef(r.key), r.value), r.source, r.env])), "", "order: flag > env > tree (strom.json) > config > default", `change: strom config set <key> <value> [--for-tree] · strom config unset <key> [--for-tree]`, `other env: ${OTHER_ENV.map((e) => e.env).join(" ")} (strom help config where)`, `config file: ${ctx.display(configFile(ctx.env))}`);
755
- return { text, data: { settings: rows, otherEnv: OTHER_ENV, file: configFile(ctx.env) } };
767
+ const invalid = rows.filter((r) => r.invalid).map((r) => r.key);
768
+ const text = lines(table(rows.map((r) => [r.key, `${display(ctx, settingDef(r.key), r.value)}${r.invalid ? " (invalid)" : ""}`, r.source, r.env])), "", ...(invalid.length ? [APP_URL_INVALID_SETTING, ""] : []), "order: flag > env > tree (strom.json) > config > default", `change: strom config set <key> <value> [--for-tree] · strom config unset <key> [--for-tree]`, `other env: ${OTHER_ENV.map((e) => e.env).join(" ")} (strom help config where)`, `config file: ${ctx.display(configFile(ctx.env))}`);
769
+ return { text, data: { settings: rows, otherEnv: OTHER_ENV, file: configFile(ctx.env), ...(invalid.length ? { invalid } : {}) } };
756
770
  },
757
771
  description: "Settings a tree can carry (lang, agent, model.*, brief.budget, run.minutes) are set per tree with --for-tree.\n" +
758
772
  OTHER_ENV.map((e) => `${e.env}: ${e.description}`).join("\n"),
@@ -764,7 +778,11 @@ register({
764
778
  run(ctx, { args }) {
765
779
  const def = settingDef(args[0]);
766
780
  const r = effective(ctx, def);
767
- return { text: r.value === undefined ? "" : display(ctx, def, r.value), data: { key: def.key, value: r.value, source: r.source } };
781
+ const shown = r.value === undefined ? "" : display(ctx, def, r.value);
782
+ // a strom.app.url that is no address of the app: as found, and said so (B1-e)
783
+ if (r.invalid)
784
+ return { text: lines(`${shown} (invalid)`, APP_URL_INVALID_SETTING), data: { key: def.key, value: r.value, source: r.source, invalid: [def.key] } };
785
+ return { text: shown, data: { key: def.key, value: r.value, source: r.source } };
768
786
  },
769
787
  }, {
770
788
  path: ["config", "set"],
@@ -778,7 +796,7 @@ register({
778
796
  examples: ["strom config set lang cs", 'strom config set shared "/Volumes/Big/strom-shared"', "strom config set model.vision opus --for-tree", "strom config set run.minutes 45"],
779
797
  run: async (ctx, { args, opts }) => {
780
798
  const def = settingDef(args[0]);
781
- const value = checkValue(def, args[1], (p) => ctx.resolvePath(p));
799
+ const value = checkValue(def, args[1], (p) => ctx.resolvePath(p), ctx.env);
782
800
  // In their own terminal the user reads what full means and says yes once more.
783
801
  if (def.key === "agent.permissions" && value === "full" && ctx.settings.agentPermissions() !== "full" && !opts["for-tree"] && ctx.interactive && !isAgent(ctx.env)) {
784
802
  const lang = ctx.uiLang();
@@ -19,7 +19,7 @@ import { assertIntact } from "../core/integrity.js";
19
19
  import { agentBrowser } from "../core/connector.js";
20
20
  import { browserNote } from "./connectors.js";
21
21
  import { offerLinks } from "../cli/wizard.js";
22
- import { appOpensLinks, appOpensResearch, importAppUrl, installedStromApp, liveAppUrl, noticeStromApp, STROM_APP_URL, stromAppUrl } from "../core/stromapp.js";
22
+ import { appOpensLinks, appOpensResearch, appUrlSetting, importAppUrl, installedStromApp, liveAppUrl, liveAppUrlShown, noticeStromApp, stromAppUrl } from "../core/stromapp.js";
23
23
  import { forgetLive, liveRunning, serveLive, startLive, stopLive } from "../core/live.js";
24
24
  import { openForUser } from "../core/open.js";
25
25
  import { createShortcut, openInNewTerminal } from "../core/shortcut.js";
@@ -72,6 +72,21 @@ function bridgeTree(ctx) {
72
72
  const tree = ctx.tree();
73
73
  return { root: tree.root, lang: tree.lang };
74
74
  }
75
+ /**
76
+ * What strom live and live start say of the app that follows the bridge: its address — or, where strom.app.url says no
77
+ * address of the app, how that stands, and no address built on it with the bridge's secret (the bridge runs all the same).
78
+ */
79
+ function liveAppLine(ctx, lang, bridge) {
80
+ const url = liveAppUrlShown(bridge, ctx.settings);
81
+ if (url)
82
+ return ui(lang, "ui.live.app", { url });
83
+ const bad = appUrlSetting(ctx.settings).invalid;
84
+ return ui(lang, bad.source === "env" ? "ui.doc.appurl.env" : "ui.doc.appurl.config", { value: bad.value });
85
+ }
86
+ function liveAppData(ctx, bridge) {
87
+ const url = liveAppUrlShown(bridge, ctx.settings);
88
+ return url ? { app: url } : {};
89
+ }
75
90
  register({
76
91
  path: ["chat"],
77
92
  summary: "Talk with the AI agent about the research — in its desktop app or in this terminal, set up the way you chose",
@@ -209,7 +224,7 @@ register({
209
224
  description: "In a tree, with an app that can take it (from its version 3.0.0), the app opens this research itself — the first time as\n" +
210
225
  "a new tree, after that the same tree updated — and follows it while somebody is at work on it (the live bridge).\n" +
211
226
  `Otherwise the results are ready for it in output/tree-strom.ged (in the app: Import). Each entry comes with its image,\n` +
212
- `cut out of its scan (the settings excerpts.*; excerpts.for none: without). Web: ${STROM_APP_URL}`,
227
+ `cut out of its scan (the settings excerpts.*; excerpts.for none: without). Web: {appUrl}`,
213
228
  options: [
214
229
  { name: "live", type: "boolean", description: "the app follows this research while it goes on (the live bridge)" },
215
230
  { name: "images", type: "boolean", description: "each entry with its image (the default; kept for older scripts)" },
@@ -217,6 +232,9 @@ register({
217
232
  examples: ["strom app", "strom app --live", "strom app install"],
218
233
  async run(ctx, { opts }) {
219
234
  const lang = ctx.uiLang();
235
+ // a strom.app.url that is no address of the app (the variable, or written by hand into the settings): refused
236
+ // before anything starts — nothing opened, no address built on it with the bridge's secret (B1-c)
237
+ stromAppUrl(ctx.settings);
220
238
  const root = appOpensResearch(ctx.settings) ? ctx.locateTree() : undefined;
221
239
  if (root) {
222
240
  const tree = Tree.open(root, ctx.env);
@@ -308,7 +326,7 @@ register({
308
326
  const lang = tree.lang;
309
327
  const info = liveRunning(tree.root);
310
328
  return info
311
- ? { text: lines(ui(lang, "ui.live.runs", { url: info.url }), ui(lang, "ui.live.app", { url: liveAppUrl(info.url, ctx.settings) }), ui(lang, "ui.live.stophow")), data: { running: true, ...info, app: liveAppUrl(info.url, ctx.settings) } }
329
+ ? { text: lines(ui(lang, "ui.live.runs", { url: info.url }), liveAppLine(ctx, lang, info.url), ui(lang, "ui.live.stophow")), data: { running: true, ...info, ...liveAppData(ctx, info.url) } }
312
330
  : { text: ui(lang, "ui.live.none"), data: { running: false } };
313
331
  },
314
332
  }, {
@@ -324,8 +342,8 @@ register({
324
342
  if (!info)
325
343
  throw new StromError("the bridge did not start", { hint: "strom live serve shows why", code: "live.not-started" });
326
344
  return {
327
- text: lines(ui(lang, "ui.live.runs", { url: info.url }), ui(lang, "ui.live.app", { url: liveAppUrl(info.url, ctx.settings) }), info.moved ? ui(lang, "ui.live.moved") : undefined),
328
- data: { ...info, app: liveAppUrl(info.url, ctx.settings) },
345
+ text: lines(ui(lang, "ui.live.runs", { url: info.url }), liveAppLine(ctx, lang, info.url), info.moved ? ui(lang, "ui.live.moved") : undefined),
346
+ data: { ...info, ...liveAppData(ctx, info.url) },
329
347
  };
330
348
  },
331
349
  }, {
@@ -19,7 +19,7 @@ import { isArchive } from "../core/mode.js";
19
19
  import { applySync, discardReceived, nothingSince, withoutImages, planSync, readTreeFile, receivedAll, receivedPending, receivedSince, receivedOf, settleReceived, SYNC_INBOX, syncConflicts, undoReceived, undoSync } from "../core/sync.js";
20
20
  import { labels } from "../gedcom/labels.js";
21
21
  import { startLive } from "../core/live.js";
22
- import { appSendsChanges, sendAppUrl } from "../core/stromapp.js";
22
+ import { appSendsChanges, sendAppUrl, stromAppUrl } from "../core/stromapp.js";
23
23
  import { appWindow, openAppIn, replaceGone } from "../core/appbrowser.js";
24
24
  import { isAgent } from "../core/which.js";
25
25
  /**
@@ -181,6 +181,8 @@ async function fromApp(ctx, tree, lang) {
181
181
  const root = tree.root;
182
182
  if (!appSendsChanges(ctx.settings))
183
183
  return { text: ui(lang, "ui.sync.nosend") };
184
+ // a strom.app.url that is no address of the app: refused before the bridge starts — no address built on it (B1-c)
185
+ stromAppUrl(ctx.settings);
184
186
  // what the app sends the bridge writes at once (unless the user reviews each send): what came of it is said, not shown to write
185
187
  const atOnce = isArchive(tree) || !ctx.settings.syncReview(tree.config);
186
188
  const info = startLive(root, ctx.env, { current: true });
@@ -6,7 +6,7 @@
6
6
  // Never one it cannot reach strom from (Safari). A tree handed over (`holdsTree`: ?adopt=) waits in the storage of
7
7
  // the browser it came from: there only — its app, else its tab — while that browser is here.
8
8
  import { appBrowsers, browserKind, browserName, defaultBrowser, openInBrowser, openWebApp } from "./chromium.js";
9
- import { installedStromApps, stromAppUrl } from "./stromapp.js";
9
+ import { appUrlSetting, installedStromApps } from "./stromapp.js";
10
10
  /**
11
11
  * Where the Strom app opens on this computer — nothing when no browser here reaches strom. `holdsTree`: the address
12
12
  * needs the app's storage in the browser its tree came from (a tree handed over), so another browser's app is no use.
@@ -14,7 +14,7 @@ import { installedStromApps, stromAppUrl } from "./stromapp.js";
14
14
  export function appWindow(settings, env, platform = process.platform, opts = {}) {
15
15
  const reach = appBrowsers(env, platform);
16
16
  // the copies of the app (strom.app.url) installed from a browser that is here
17
- const apps = installedStromApps(env, platform, stromAppUrl(settings)).filter((a) => a.appId && a.browser && reach.some((b) => b.name === a.browser));
17
+ const apps = installedStromApps(env, platform, appUrlSetting(settings).url).filter((a) => a.appId && a.browser && reach.some((b) => b.name === a.browser));
18
18
  const appOf = (b) => {
19
19
  const a = b && apps.find((x) => x.browser === b.name);
20
20
  return a?.appId && a.browser ? { browser: a.browser, appId: a.appId, ...(a.profile ? { profile: a.profile } : {}) } : undefined;