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

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,10 @@
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",
281
+ "ui.doc.appurl.env.fix": "proměnnou STROM_APP_URL nastavit na adresu aplikace Strom ({web}, nebo její kopie na tomto počítači http://127.0.0.1:<port>/), nebo odebrat",
278
282
  "ui.doc.tree.ok": "{name}: data v pořádku a zapečetěná",
279
283
  "ui.doc.tree.bad": "{name}: problémů {n}",
280
284
  "ui.doc.allok": "Všechno potřebné je na místě.",
@@ -654,7 +658,7 @@
654
658
  "ui.help.archive.stats": "Archiv v kostce: osoby, jak jisté jsou údaje, předkové v každé generaci",
655
659
  "ui.help.archive.pack": "Zabalit archiv do jednoho souboru ZIP, aby v něm mohl pokračovat někdo jiný",
656
660
  "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)",
661
+ "ui.help.title": "{strom} <příkaz> – co člověk se stromem dělá (nebo jen {strom}: menu)",
658
662
  "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
663
  "ui.help.more.archive": "Nápověda k jednomu příkazu: strom help <příkaz> --human",
660
664
  "ui.help.examples": "Příklady:",
@@ -846,11 +850,12 @@
846
850
  "ui.live.app": " aplikace Strom ho sleduje na adrese: {url}",
847
851
  "ui.live.stophow": " zastavit: strom live stop",
848
852
  "ui.live.none": "most neběží – strom live start (nebo strom app --live)",
849
- "ui.live.moved": " má novou adresu (starý port byl obsazený): aplikace Strom ji najde, až v ní výzkum znovu otevře – strom app --live",
853
+ "ui.live.moved": " má novou adresu: aplikace Strom ji najde, až v ní výzkum znovu otevře – strom app --live",
850
854
  "ui.live.stopped": "most se zastavil",
851
855
  "ui.live.killed": "most neskončil, když byl požádán: byl ukončen natvrdo",
852
856
  "ui.live.noneran": "žádný most neběžel",
853
857
  "ui.live.forgotten": "jeho adresa je zapomenutá: další most dostane novou",
858
+ "ui.live.newsecret": " další most dostane novou tajnou adresu: aplikace Strom ji dostane při dalším otevření výzkumu – strom app",
854
859
  "ui.live.ended": "most skončil",
855
860
  "ui.guide.archive": "Tento rodokmen je archiv: data přicházejí z aplikace Strom. Co v něm je: strom stats · strom person card <kdo> · strom pedigree <kdo> · strom recent. Výzkum zapíná jen uživatel: strom mode research (nebo menu).",
856
861
  "ui.setup.links.archive": "Dovolit aplikaci Strom otevírat archiv na tomto počítači? (Jedním klikem pošle úpravy zpět nebo otevře výřez v plné kvalitě – vždy se tu nejdřív zeptá.)",
@@ -1496,6 +1501,10 @@
1496
1501
  "ui.error.arg.missing": "chybí <{arg}>",
1497
1502
  "ui.error.arg.missing.hint": "strom help {cmd}",
1498
1503
  "ui.error.prefix": "chyba",
1504
+ "ui.error.config.app-url": "neplatné strom.app.url „{value}“: není to adresa aplikace Strom",
1505
+ "ui.error.config.app-url.hint": "aplikace Strom: {web}, nebo její kopie na tomto počítači http://127.0.0.1:<port>/",
1506
+ "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",
1507
+ "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
1508
  "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
1509
  "ui.error.backup.failed.hint": "Uvolnit místo na disku ({where}) a spustit strom znovu – nebo se vrátit: strom update --channel {back}",
1501
1510
  "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 +1596,7 @@
1587
1596
  "ui.arg.tree-name": "název",
1588
1597
  "ui.arg.url": "adresa",
1589
1598
  "ui.arg.value": "hodnota",
1599
+ "ui.arg.address": "adresa",
1590
1600
  "ui.arg.what": "co",
1591
1601
  "ui.arg.where": "kde",
1592
1602
  "ui.arg.who": "kdo",
@@ -1614,7 +1624,7 @@
1614
1624
  "ui.error.event.needs-reason.hint": "např. --reason \"záznam S0012 uvádí 1811, ne 1813\"",
1615
1625
  "ui.error.family.needs-reason": "změna toho, kdo do rodiny patří nebo jak, potřebuje --reason",
1616
1626
  "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",
1627
+ "ui.error.person.needs-reason": "změna jména, jeho titulu nebo známého pohlaví potřebuje --reason",
1618
1628
  "ui.error.person.needs-reason.hint": "např. --reason \"špatně přečteno: matrika má Novák, ne Nowak\"",
1619
1629
  "ui.error.retract.needs-reason": "stažení potřebuje --reason",
1620
1630
  "ui.error.retract.needs-reason.hint": "např. --reason \"špatně přečteno: zápis uvádí Annu, ne Antona\"",
@@ -275,6 +275,10 @@
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",
281
+ "ui.doc.appurl.env.fix": "die Variable STROM_APP_URL auf eine Adresse der Strom-App setzen ({web}, oder eine Kopie auf diesem Computer http://127.0.0.1:<port>/) oder entfernen",
278
282
  "ui.doc.tree.ok": "{name}: Daten in Ordnung und versiegelt",
279
283
  "ui.doc.tree.bad": "{name}: Probleme: {n}",
280
284
  "ui.doc.allok": "Alles Nötige ist vorhanden.",
@@ -654,7 +658,7 @@
654
658
  "ui.help.archive.stats": "Das Archiv auf einen Blick: Personen, wie sicher die Angaben sind, Vorfahren je Generation",
655
659
  "ui.help.archive.pack": "Das Archiv in eine ZIP-Datei packen, damit jemand anderes weitermachen kann",
656
660
  "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ü)",
661
+ "ui.help.title": "{strom} <Befehl> – was ein Mensch mit strom tut (oder nur {strom}: das Menü)",
658
662
  "ui.help.more": "Hilfe zu einem Befehl: strom help <Befehl> --human · alle Befehle auf Englisch (was ein Agent liest): strom help",
659
663
  "ui.help.more.archive": "Hilfe zu einem Befehl: strom help <Befehl> --human",
660
664
  "ui.help.examples": "Beispiele:",
@@ -846,11 +850,12 @@
846
850
  "ui.live.app": " die Strom-App folgt ihr unter: {url}",
847
851
  "ui.live.stophow": " anhalten: strom live stop",
848
852
  "ui.live.none": "die Brücke läuft nicht – strom live start (oder strom app --live)",
849
- "ui.live.moved": " sie hat eine neue Adresse (der alte Port war belegt): die Strom-App findet sie, wenn die Forschung darin wieder geöffnet wird – strom app --live",
853
+ "ui.live.moved": " sie hat eine neue Adresse: die Strom-App findet sie, wenn die Forschung darin wieder geöffnet wird – strom app --live",
850
854
  "ui.live.stopped": "die Brücke ist angehalten",
851
855
  "ui.live.killed": "die Brücke endete nicht auf Bitte: sie wurde hart beendet",
852
856
  "ui.live.noneran": "keine Brücke lief",
853
857
  "ui.live.forgotten": "ihre Adresse ist vergessen: die nächste Brücke bekommt eine neue",
858
+ "ui.live.newsecret": " die nächste Brücke bekommt eine neue geheime Adresse: die Strom-App bekommt sie beim nächsten Öffnen der Forschung – strom app",
854
859
  "ui.live.ended": "die Brücke ist beendet",
855
860
  "ui.guide.archive": "Dieser Stammbaum ist ein Archiv: die Daten kommen aus der Strom-App. Was er enthält: strom stats · strom person card <wer> · strom pedigree <wer> · strom recent. Die Forschung schaltet nur der Benutzer ein: strom mode research (oder das Menü).",
856
861
  "ui.setup.links.archive": "Der Strom-App erlauben, das Archiv auf diesem Computer zu öffnen? (Ein Klick schickt die Änderungen zurück oder öffnet einen Ausschnitt in voller Qualität – jeweils erst hier gefragt.)",
@@ -1496,6 +1501,10 @@
1496
1501
  "ui.error.arg.missing": "<{arg}> fehlt",
1497
1502
  "ui.error.arg.missing.hint": "strom help {cmd}",
1498
1503
  "ui.error.prefix": "Fehler",
1504
+ "ui.error.config.app-url": "ungültige strom.app.url „{value}“: keine Adresse der Strom-App",
1505
+ "ui.error.config.app-url.hint": "die Strom-App: {web}, oder eine Kopie auf diesem Computer http://127.0.0.1:<port>/",
1506
+ "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",
1507
+ "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
1508
  "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
1509
  "ui.error.backup.failed.hint": "Platz auf dem Datenträger freimachen ({where}) und strom erneut starten – oder zurück: strom update --channel {back}",
1501
1510
  "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 +1596,7 @@
1587
1596
  "ui.arg.tree-name": "Name",
1588
1597
  "ui.arg.url": "Adresse",
1589
1598
  "ui.arg.value": "Wert",
1599
+ "ui.arg.address": "Adresse",
1590
1600
  "ui.arg.what": "was",
1591
1601
  "ui.arg.where": "wo",
1592
1602
  "ui.arg.who": "wer",
@@ -1614,7 +1624,7 @@
1614
1624
  "ui.error.event.needs-reason.hint": "z. B. --reason \"Eintrag S0012 nennt 1811, nicht 1813\"",
1615
1625
  "ui.error.family.needs-reason": "wer zur Familie gehört oder wie, ändert sich nur mit --reason",
1616
1626
  "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",
1627
+ "ui.error.person.needs-reason": "ein Name, sein Titel oder ein bekanntes Geschlecht ändert sich nur mit --reason",
1618
1628
  "ui.error.person.needs-reason.hint": "z. B. --reason \"falsch gelesen: im Register steht Novák, nicht Nowak\"",
1619
1629
  "ui.error.retract.needs-reason": "Zurückziehen braucht --reason",
1620
1630
  "ui.error.retract.needs-reason.hint": "z. B. --reason \"falsch gelesen: der Eintrag nennt Anna, nicht Anton\"",
@@ -12,12 +12,15 @@ import { writeFileAtomic } from "../core/json.js";
12
12
  import { PROFILES, SELF_READING } from "./profiles.js";
13
13
  import { isArchive } from "../core/mode.js";
14
14
  import { Settings } from "../core/config.js";
15
+ import { agentAppUrl, appSite } from "../core/stromapp.js";
15
16
  import { configDir } from "../core/paths.js";
16
17
  import { agentBrowser } from "../core/connector.js";
17
18
  import { CHROME_ALLOW, CHROME_DENY, chromeDomain } from "../core/browser.js";
18
19
  export const MARKER = "<!-- strom: generated above this line (strom agents sync); your own notes below are kept -->";
19
20
  function agentsMd(tree) {
20
21
  const lang = langName(tree.config.lang);
22
+ // the Strom app's address as strom app opens it now (the beta its beta, strom.app.url where it says another)
23
+ const app = agentAppUrl(tree.env);
21
24
  return `# Family research: ${tree.config.name}
22
25
 
23
26
  This folder is a family tree researched with **strom**, a command-line tool
@@ -99,12 +102,12 @@ they ask.
99
102
  - **What waits for them** (\`strom\` shows it): tell them plainly what to do
100
103
  and where, one thing at a time.
101
104
  - **Results and the Strom app**: \`output/tree-strom.ged\` is the family tree
102
- for the Strom app (https://stromapp.info) — strom's companion: a free family
105
+ for the Strom app (${appSite(app)}) — strom's companion: a free family
103
106
  tree app, no account, their data stay on their computer; it shows the tree,
104
107
  the sources, a map, a family book. When they want to see the results, or
105
108
  when \`strom\` says to offer it (it does once, not in every conversation),
106
109
  suggest it gently, in a sentence or two: best installed as an app from the browser, from
107
- https://stromapp.info/run/ — \`strom app install\` opens it there and says
110
+ ${app} — \`strom app install\` opens it there and says
108
111
  where to click; it works offline then; \`strom app\` opens it, with this
109
112
  research when the app can take it — run by you, the app then follows the
110
113
  research live, what you record shows there by itself (else in the app:
@@ -16,9 +16,11 @@ import path from "node:path";
16
16
  import { isolated, userHome } from "../core/paths.js";
17
17
  import { writeFileAtomic } from "../core/json.js";
18
18
  import { installation } from "../core/self.js";
19
+ import { agentAppUrl, appSite } from "../core/stromapp.js";
19
20
  const BEGIN = "<!-- strom: begin (strom agents install; removed by strom agents uninstall) -->";
20
21
  const END = "<!-- strom: end -->";
21
- export const GLOBAL_TEXT = `# strom — family history research
22
+ /** What the agents are taught; app: the Strom app's address it names — the one `strom app` opens when it is written (agentAppUrl). */
23
+ export const globalText = (app) => `# strom — family history research
22
24
 
23
25
  strom (the \`strom\` command) is installed on this computer: a tool for
24
26
  researching a family's history with you as the researcher — people, parish
@@ -57,11 +59,11 @@ mentions strom, a family tree or the Strom app.
57
59
  8. Scans come through strom. An archive the research needs has no
58
60
  downloader (connector) yet: build one yourself, unasked
59
61
  (\`strom connector new\`), and tell the user in a sentence.
60
- 9. The Strom app (https://stromapp.info) is strom's companion: a free family
62
+ 9. The Strom app (${appSite(app)}) is strom's companion: a free family
61
63
  tree app, no account, the data stay on the user's computer. The research's
62
64
  result (\`output/tree-strom.ged\`) opens in it. When the user wants to see
63
65
  the tree (or \`strom\` says to offer it — once), suggest it gently — best installed as an app from the browser,
64
- from https://stromapp.info/run/ (\`strom app install\` opens it there; it
66
+ from ${app} (\`strom app install\` opens it there; it
65
67
  then works offline); \`strom app\` opens it — with the research, followed
66
68
  live while you work, when the app can take it. Without it, the user can ask
67
69
  you about anyone in the tree: answer from \`strom person show\` and the like.
@@ -149,7 +151,9 @@ export function globalTargets(env) {
149
151
  if (isolated(env))
150
152
  return [];
151
153
  const home = userHome(env);
152
- return [
154
+ // the app's address as strom app opens it now: a run of another channel (its first) writes the texts again
155
+ const app = agentAppUrl(env);
156
+ const targets = [
153
157
  { agent: "claude", file: path.join(env.CLAUDE_CONFIG_DIR ?? path.join(home, ".claude"), "skills", "strom", "SKILL.md"), kind: "own" },
154
158
  { agent: "claude", file: path.join(env.CLAUDE_CONFIG_DIR ?? path.join(home, ".claude"), "settings.json"), kind: "allow" },
155
159
  { agent: "codex", file: path.join(env.CODEX_HOME ?? path.join(home, ".codex"), "AGENTS.md"), kind: "block" },
@@ -161,13 +165,14 @@ export function globalTargets(env) {
161
165
  { agent: "grok", file: path.join(grokDir(env), "skills", "strom", "SKILL.md"), kind: "own" },
162
166
  { agent: "grok", file: path.join(grokDir(env), "config.toml"), kind: "allow" },
163
167
  ];
168
+ return targets.map((t) => ({ ...t, app }));
164
169
  }
165
- const SKILL = `---
170
+ const skill = (app) => `---
166
171
  name: strom
167
172
  description: Family history and genealogy research with the strom command — ancestors, family trees, parish registers and archives, evidence, GEDCOM and the Strom app. Use when the user wants to research their family or mentions strom or the Strom app.
168
173
  ---
169
174
 
170
- ${GLOBAL_TEXT}`;
175
+ ${globalText(app)}`;
171
176
  function read(file) {
172
177
  try {
173
178
  return fs.readFileSync(file, "utf8");
@@ -282,10 +287,10 @@ export function installGlobal(t) {
282
287
  next = JSON.stringify(s, null, 2) + "\n";
283
288
  }
284
289
  else if (t.kind === "own")
285
- next = t.agent === "claude" || t.agent === "grok" ? SKILL : GLOBAL_TEXT;
290
+ next = t.agent === "claude" || t.agent === "grok" ? skill(t.app) : globalText(t.app);
286
291
  else {
287
292
  const rest = cur ? withoutBlock(cur).replace(/\s+$/, "") : "";
288
- next = `${rest ? `${rest}\n\n` : ""}${BEGIN}\n${GLOBAL_TEXT}${END}\n`;
293
+ next = `${rest ? `${rest}\n\n` : ""}${BEGIN}\n${globalText(t.app)}${END}\n`;
289
294
  }
290
295
  if (cur === next)
291
296
  return false;
@@ -16,7 +16,7 @@ import { readJsonIfExists } from "../core/json.js";
16
16
  import { foldText } from "../core/text.js";
17
17
  import { currentSession } from "../core/session.js";
18
18
  import { isAgent } from "../core/which.js";
19
- import { systemDialog, WAIT_SECONDS } from "../core/dialog.js";
19
+ import { canShowDialog, systemDialog, WAIT_SECONDS } from "../core/dialog.js";
20
20
  import { UI, ui } from "./ui.js";
21
21
  export class Context {
22
22
  env;
@@ -179,7 +179,8 @@ export class Context {
179
179
  const agent = isAgent(this.env);
180
180
  if (this.interactive && !agent)
181
181
  return "terminal";
182
- if (this.env.STROM_NONINTERACTIVE !== "1" && opts.window !== false) {
182
+ // a window only where one can be shown (the test's own answer stands for one): else nothing said to wait in it
183
+ if (this.env.STROM_NONINTERACTIVE !== "1" && opts.window !== false && (this.io.dialog || canShowDialog(this.env))) {
183
184
  const lang = this.uiLang();
184
185
  const text = { title: ui(lang, "ui.dialog.title"), question: says ?? question, yes: ui(lang, "ui.dialog.yes"), no: ui(lang, "ui.dialog.no") };
185
186
  // said first where it runs: a window can be behind others, and the command waits for it without a word
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,10 @@ 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",
248
+ "ui.doc.appurl.env.fix": "the variable STROM_APP_URL set to an address of the Strom app ({web}, or a copy on this computer http://127.0.0.1:<port>/), or removed",
245
249
  "ui.doc.tree.ok": "{name}: data consistent and sealed",
246
250
  "ui.doc.tree.bad": "{name}: {n} problem(s)",
247
251
  "ui.doc.allok": "Everything needed is in place.",
@@ -561,7 +565,7 @@ export const UI = {
561
565
  "ui.help.archive.stats": "The archive at a glance: people, how sure the facts are, ancestors in each generation",
562
566
  "ui.help.archive.pack": "Pack the archive into one ZIP file for someone to go on with it",
563
567
  "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)",
568
+ "ui.help.title": "{strom} <command> — what a person does with strom (or just {strom}: the menu)",
565
569
  "ui.help.more": "Help for one command: strom help <command> --human · every command in English (what an agent reads): strom help",
566
570
  "ui.help.more.archive": "Help for one command: strom help <command> --human",
567
571
  "ui.help.examples": "Examples:",
@@ -762,11 +766,12 @@ export const UI = {
762
766
  "ui.live.app": " the Strom app following it: {url}",
763
767
  "ui.live.stophow": " stop: strom live stop",
764
768
  "ui.live.none": "the bridge does not run — strom live start (or strom app --live)",
765
- "ui.live.moved": " its address is new (its old port was taken): the Strom app finds it when the research is opened in it again — strom app --live",
769
+ "ui.live.moved": " its address is new: the Strom app finds it when the research is opened in it again — strom app --live",
766
770
  "ui.live.stopped": "the bridge stopped",
767
771
  "ui.live.killed": "the bridge did not end when asked: it was ended for good",
768
772
  "ui.live.noneran": "no bridge ran",
769
773
  "ui.live.forgotten": "its address is forgotten: the next bridge gets a new one",
774
+ "ui.live.newsecret": " the next bridge gets a new secret address: the Strom app gets it when the research is opened in it again — strom app",
770
775
  "ui.live.ended": "the bridge ended",
771
776
  "ui.guide.archive": "This family tree is an archive: the data come from the Strom app. What it holds: strom stats · strom person card <who> · strom pedigree <who> · strom recent. Research is switched on by the user alone: strom mode research (or the menu).",
772
777
  "ui.setup.links.archive": "Let the Strom app open the archive on this computer? (One click sends the edits back or opens an excerpt in full quality — each asked here first.)",
@@ -1346,6 +1351,10 @@ export const UI = {
1346
1351
  "ui.error.arg.missing": "<{arg}> is missing",
1347
1352
  "ui.error.arg.missing.hint": "strom help {cmd}",
1348
1353
  "ui.error.prefix": "error",
1354
+ "ui.error.config.app-url": "invalid strom.app.url \"{value}\": no address of the Strom app",
1355
+ "ui.error.config.app-url.hint": "the Strom app: {web}, or a copy on this computer http://127.0.0.1:<port>/",
1356
+ "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",
1357
+ "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
1358
  "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
1359
  "ui.error.backup.failed.hint": "Free room on the disk ({where}), then run strom again — or go back: strom update --channel {back}",
1351
1360
  "ui.error.backup.failed.update": "The backup before going from {from} to {to} could not be made: {detail}. Nothing was installed.",
@@ -1421,6 +1430,7 @@ export const UI = {
1421
1430
  "ui.arg.tree-name": "tree name",
1422
1431
  "ui.arg.url": "url",
1423
1432
  "ui.arg.value": "value",
1433
+ "ui.arg.address": "address",
1424
1434
  "ui.arg.what": "what",
1425
1435
  "ui.arg.where": "where",
1426
1436
  "ui.arg.who": "who",
@@ -1449,7 +1459,7 @@ export const UI = {
1449
1459
  "ui.error.event.needs-reason.hint": "e.g. --reason \"record S0012 gives 1811, not 1813\"",
1450
1460
  "ui.error.family.needs-reason": "changing who belongs to a family, or how, needs --reason",
1451
1461
  "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",
1462
+ "ui.error.person.needs-reason": "changing a name, its title or a known sex needs --reason",
1453
1463
  "ui.error.person.needs-reason.hint": "e.g. --reason \"misread: the register has Novák, not Nowak\"",
1454
1464
  "ui.error.retract.needs-reason": "retracting needs --reason",
1455
1465
  "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
  }
@@ -3,7 +3,9 @@
3
3
  // sessions and costs tokens every time. Every command in it must exist
4
4
  // (test/cli/examples.test.ts checks them against the registry).
5
5
  import { langName } from "../core/lang.js";
6
- export function guideText(lang) {
6
+ import { appSite } from "../core/stromapp.js";
7
+ /** The guide; appUrl: the Strom app's address it names — the one `strom app` opens (appUrlShown). */
8
+ export function guideText(lang, appUrl) {
7
9
  const language = lang
8
10
  ? `The research language of this tree is ${langName(lang)} (${lang}): talk to the user in ${langName(lang)} and write notes, tasks and stories in ${langName(lang)}. Keep transcripts of records in their original language.`
9
11
  : "Talk to the user in their language. Set it as the research language: strom setup --lang <code> (cs, en, de, pl, …).";
@@ -181,11 +183,11 @@ output/tree-strom.ged (for the Strom app) — both from the same evidence, also
181
183
  Changes are committed automatically; you never run git yourself.
182
184
 
183
185
  THE STROM APP — where the user sees the result
184
- The Strom app (https://stromapp.info) is strom's companion: a free family tree app, no account,
186
+ The Strom app (${appSite(appUrl)}) is strom's companion: a free family tree app, no account,
185
187
  the family's data stay on their computer; it shows the tree, the sources, a map, a family book.
186
188
  When the user wants to see the results, or when strom says to offer it (once, not in every
187
189
  conversation), suggest it gently, in a sentence or two — best installed as an app from the browser, then it works offline:
188
- https://stromapp.info/run/ the app itself: opened in the browser, installed from there (the
190
+ ${appUrl} the app itself: opened in the browser, installed from there (the
189
191
  install icon at the end of the address bar; Safari: File → Add to Dock)
190
192
  strom app install opens it there and says where to click
191
193
  strom app opens it — with this research when the app can take it (strom says so), and
@@ -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
@@ -209,7 +209,7 @@ register({
209
209
  const said = ui(ctx.tree().lang, "ui.guide.archive");
210
210
  return { text: said, data: { guide: said, lang, archive: true } };
211
211
  }
212
- const text = guideText(lang);
212
+ const text = guideText(lang, appUrlShown(ctx.settings).url);
213
213
  return { text, data: { guide: text, lang } };
214
214
  },
215
215
  }, {
@@ -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
  });