strom-research 1.15.0-beta.4 → 1.15.0-beta.6

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.
@@ -0,0 +1,45 @@
1
+ # Co je nového ve Strom výzkumu
2
+
3
+ [English](CHANGELOG.md) · Čeština · [Deutsch](CHANGELOG.de.md)
4
+
5
+ Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou na
6
+ [GitHub Releases](https://github.com/ACiDekCZ/strom-research/releases) (anglicky).
7
+
8
+ ## 1.15.0
9
+
10
+ *Patří k aplikaci Strom 3.12.0.*
11
+
12
+ - **Archiv Stromu podle volby, i s AI agentem.** Průvodce nastavením se ptá, zda nové rodokmeny budou výzkumem
13
+ s agentem, nebo jen Archivem Stromu; v nastavení jde volbu změnit pro jeden rodokmen nebo pro nové.
14
+ - **Výzkum čeká na svého agenta.** Rodokmen, jehož AI agent na tomto počítači není, ukládá, co posílá aplikace Strom,
15
+ jako Archiv Stromu. Jakmile je agent nainstalovaný, výzkum pokračuje sám.
16
+ - **Jedna kniha archivu se stahuje jednou.** Kniha, která už ve výzkumu je, se pojmenuje místo nového zápisu
17
+ a stahování, takže archiv nikdy nedostane dvakrát žádost o tytéž snímky.
18
+ - **Rychlejší čtení.** Snímky čte současně pět čtenářů (nastavení `read.parallel`, 1 až 10) a dlouhé stahování
19
+ ukazuje, kolik už je hotovo („40/109 snímků“). Čtenáři začnou už během stahování knihy: snímky se registrují
20
+ po deseti a co nepřišlo, je řečeno přesně; Ctrl-C ponechá, co už přišlo. Archiv dostane stejné žádosti ve stejném
21
+ tempu. Kniha se může stahovat i na pozadí, zatímco agent pracuje dál (`strom fetch --background`), nikdy z archivu,
22
+ který dovoluje jen tempo člověka. Hledání jednoho zápisu může skončit u nálezu (`strom read --until-found`):
23
+ přečtené snímky jsou řečeny přesně a jen ty se počítají jako prohledané.
24
+ - **Rodokmeny z jiných programů.** Soubor GEDCOM uložený se značkou BOM nebo v UTF-16 (tak ho ukládají některé programy
25
+ ve Windows) se čte jako rodokmen; rodokmen jiného výzkumu se převezme jako cizí.
26
+ - **Živé sledování končí čistě** na každém systému, i ve Windows, a aplikace Strom se dozví, že skončilo.
27
+ - **Jeden strom na instalaci.** Jiný strom na stejném počítači (z npm nebo sestavený ze zdrojů) nikdy nepřevezme odkazy
28
+ aplikace Strom ani kanál aktualizací.
29
+ - **Co je nového.** Po `strom update` a u `strom --version` odkaz na tento přehled v jazyce výzkumu. Aplikace Strom
30
+ ukazuje body nové verze výzkumu ve svém okně „Co je nového“.
31
+
32
+ ## 1.14.0
33
+
34
+ *11. 10. 2026 · patří k aplikaci Strom 3.11.0.*
35
+
36
+ - **Podmínky každého archivu.** strom dodržuje, jak daleko každý archiv dovoluje automatický přístup: povoleno,
37
+ omezeně, jen ručně, nebo vůbec. Kde podmínky nejsou jasné, rozhodne jednou člověk. Odmítnutí archiv na den odloží.
38
+ - **Co čeká, po místech.** Věci pro člověka jsou seřazené podle druhu a seskupené podle toho, kde se vyřizují: jeden
39
+ archiv, jeden úřad, rodina, jeden web. Dopis nebo dotaz jde označit jako odeslaný.
40
+ - **Čtení starého písma.** Snímky ve velikosti, jakou každý pohled potřebuje, čtenář snímků na modelu, který si člověk
41
+ zvolil, a čtení vyladěné na vlastních doložených záznamech výzkumu.
42
+ - **S aplikací Strom 3.11.0.** Rozpor mezi úpravou v aplikaci a záznamem se rozhoduje v aplikaci; hypotézy ukazují, co
43
+ by která varianta spojila.
44
+ - **Menší podklady pro agenta, které vědí víc**, a míra uvažování je volbou člověka (`model.effort`).
45
+ - **`strom grep`** prohledává podklady, poznámky a výstupy výzkumu, i bez ohledu na diakritiku.
@@ -0,0 +1,49 @@
1
+ # Was ist neu in Strom Ahnenforschung
2
+
3
+ [English](CHANGELOG.md) · [Čeština](CHANGELOG.cs.md) · Deutsch
4
+
5
+ Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Installationszeilen stehen bei
6
+ [GitHub Releases](https://github.com/ACiDekCZ/strom-research/releases) (auf Englisch).
7
+
8
+ ## 1.15.0
9
+
10
+ *Gehört zur Strom-App 3.12.0.*
11
+
12
+ - **Ein Strom-Archiv nach Wahl, auch mit einem KI-Agenten.** Die Einrichtung fragt, ob neue Stammbäume Forschungen mit
13
+ dem Agenten sind oder nur ein Strom-Archiv; in den Einstellungen lässt sich das für einen Stammbaum oder für die
14
+ neuen ändern.
15
+ - **Eine Forschung wartet auf ihren Agenten.** Ein Stammbaum, dessen KI-Agent nicht auf diesem Computer ist, speichert,
16
+ was die Strom-App sendet, wie ein Strom-Archiv. Sobald der Agent installiert ist, geht die Forschung von selbst weiter.
17
+ - **Ein Buch eines Archivs, einmal geladen.** Ein Buch, das schon in der Forschung ist, wird genannt, statt neu
18
+ eingetragen und wieder geladen zu werden – ein Archiv wird nie zweimal um dieselben Bilder gebeten.
19
+ - **Schneller lesen.** Fünf Leser lesen die Scans gleichzeitig (die Einstellung `read.parallel`, 1 bis 10), und ein
20
+ langer Download zeigt, wie weit er ist („40/109 Bilder“). Die Leser beginnen schon, während ein Buch lädt: seine
21
+ Bilder werden zu zehnt registriert, und was nicht kam, wird genau gesagt; Strg+C behält, was schon kam. Ein Archiv
22
+ bekommt dieselben Anfragen im selben Tempo wie bisher. Ein Buch kann auch im Hintergrund laden, während der Agent
23
+ weiterarbeitet (`strom fetch --background`), nie aus einem Archiv, das nur das Tempo eines Menschen erlaubt. Die
24
+ Suche nach einem Eintrag kann beim Fund enden (`strom read --until-found`): die gelesenen Bilder werden genau gesagt,
25
+ und nur sie gelten als durchsucht.
26
+ - **Stammbäume aus anderen Programmen.** Eine GEDCOM-Datei mit Byte-Order-Mark oder in UTF-16 (so speichern sie manche
27
+ Windows-Programme) wird als Stammbaum gelesen; der Stammbaum einer anderen Forschung wird als fremder übernommen.
28
+ - **Die Live-Ansicht endet sauber** auf jedem System, auch unter Windows, und die Strom-App erfährt, dass sie endete.
29
+ - **Ein strom pro Installation.** Ein anderer strom auf demselben Computer (aus npm oder aus den Quellen gebaut)
30
+ übernimmt nie die Links der Strom-App oder den Update-Kanal.
31
+ - **Was ist neu.** Nach `strom update` und bei `strom --version` ein Link zu dieser Übersicht in der Sprache der
32
+ Forschung. Die Strom-App zeigt die Punkte einer neuen Forschungsversion in ihrem Fenster „Was ist neu“.
33
+
34
+ ## 1.14.0
35
+
36
+ *11.10.2026 · gehört zur Strom-App 3.11.0.*
37
+
38
+ - **Die Bedingungen jedes Archivs.** strom hält sich daran, wie weit jedes Archiv automatischen Zugriff erlaubt:
39
+ erlaubt, eingeschränkt, nur von Hand oder gar nicht. Wo die Bedingungen unklar sind, entscheidet einmal der Mensch.
40
+ Eine Ablehnung pausiert ein Archiv für einen Tag.
41
+ - **Was wartet, nach Orten.** Die Dinge für den Menschen sind nach Art sortiert und danach gruppiert, wo sie erledigt
42
+ werden: ein Archiv, ein Amt, die Familie, eine Website. Ein Brief oder eine Frage lässt sich als verschickt markieren.
43
+ - **Alte Handschrift lesen.** Scans in der Größe, die jede Ansicht braucht, ein Leser der Scans auf dem Modell, das der
44
+ Mensch gewählt hat, und das Lesen abgestimmt auf die eigenen belegten Einträge der Forschung.
45
+ - **Mit der Strom-App 3.11.0.** Ein Widerspruch zwischen einer Änderung in der App und einem Eintrag wird in der App
46
+ entschieden; Hypothesen zeigen, was jede Variante verbinden würde.
47
+ - **Kleinere Unterlagen für den Agenten, die mehr wissen**, und der Denkaufwand ist die Wahl des Menschen
48
+ (`model.effort`).
49
+ - **`strom grep`** durchsucht die Unterlagen, Notizen und Ausgaben der Forschung, auch über Akzente hinweg.
package/CHANGELOG.md ADDED
@@ -0,0 +1,45 @@
1
+ # What's new in Strom Research
2
+
3
+ English · [Čeština](CHANGELOG.cs.md) · [Deutsch](CHANGELOG.de.md)
4
+
5
+ The changes of each version in short. The full notes and the install lines are on
6
+ [GitHub Releases](https://github.com/ACiDekCZ/strom-research/releases).
7
+
8
+ ## 1.15.0
9
+
10
+ *Goes with the Strom app 3.12.0.*
11
+
12
+ - **A Strom archive by choice, with an AI agent too.** The setup asks whether new family trees are researches with the
13
+ agent or only a Strom archive; the settings change it for one family tree or for the new ones.
14
+ - **A research waiting for its agent.** A family tree whose AI agent is not on this computer keeps what the Strom app
15
+ sends, as a Strom archive does. Once the agent is installed, the research goes on by itself.
16
+ - **One book of an archive, fetched once.** A book already in the research is named instead of being registered and
17
+ fetched again, so an archive is never asked twice for the same images.
18
+ - **Faster reading.** Five readers read the scans at the same time (the setting `read.parallel`, 1 to 10), and a long
19
+ download says how far it is ("40/109 images"). The readers begin while a book is still downloading: its images are
20
+ registered ten at a time, and what did not come is said exactly; Ctrl-C keeps what came. An archive gets the same
21
+ requests at the same pace. A book can also be fetched in the background while the agent works on (`strom fetch
22
+ --background`), never from an archive that allows only a person's pace. A search for one entry can stop at the find
23
+ (`strom read --until-found`): the images read are said exactly, and only they count as searched.
24
+ - **Family trees from other programs.** A GEDCOM file saved with a byte order mark or in UTF-16 (as some Windows
25
+ programs save it) is read as a family tree; a family tree of another research is taken in as a foreign one.
26
+ - **The live view ends cleanly** on every system, Windows too, and the Strom app learns that it ended.
27
+ - **One strom per installation.** Another strom on the same computer (from npm, or built from the sources) never takes
28
+ over the links of the Strom app or the update channel.
29
+ - **What's new.** After `strom update` and with `strom --version`, a link to this list in the research language. The
30
+ Strom app shows the points of a new research version in its own “What's new” window.
31
+
32
+ ## 1.14.0
33
+
34
+ *2026-10-11 · goes with the Strom app 3.11.0.*
35
+
36
+ - **Each archive's terms.** strom keeps to how far each archive allows automated access: allowed, restricted, by hand
37
+ only, or not at all. Where the terms are unclear, the person decides once. A refusal pauses an archive for a day.
38
+ - **What waits, in places.** Things for the person are sorted by kind and grouped by where they are done: one archive,
39
+ one office, the family, one site. A letter or a question can be marked as sent.
40
+ - **Reading old handwriting.** Scans at the size each view needs, a reader of scans on the person's model, and the
41
+ reading tuned on the research's own proven records.
42
+ - **With the Strom app 3.11.0.** A conflict between an edit in the app and a record is decided in the app; hypotheses
43
+ show what each variant would connect.
44
+ - **A smaller brief that knows more**, and the reasoning effort is the person's choice (`model.effort`).
45
+ - **`strom grep`** searches the research's inputs, notes and output, through accents too.
package/README.md CHANGED
@@ -48,7 +48,9 @@ Next time: run `strom`, or double-click the "Strom research" shortcut.
48
48
  Updates: strom looks for a new version at most once a day and says so;
49
49
  `strom update` installs it (checked like the installer does), and with it the
50
50
  newest security release of its Node line. Installed with
51
- npm: `npm install -g strom-research@latest`.
51
+ npm: `npm install -g strom-research@latest`. What is new in each version:
52
+ [CHANGELOG.md](CHANGELOG.md) ([čeština](CHANGELOG.cs.md), [Deutsch](CHANGELOG.de.md));
53
+ `strom --version` and `strom update` link to it in the research language.
52
54
 
53
55
  ## Features
54
56
 
@@ -250,6 +250,7 @@
250
250
  "ui.tune.book.weak": "zápory na slabých skenech {n} z {of}",
251
251
  "ui.tune.book.requests": "požadavky {n}",
252
252
  "ui.tune.book.unread": "staženo a nepřečteno {n} z {of}",
253
+ "ui.tune.book.background": "na pozadí: nepřečteno {n} z {of}",
253
254
  "ui.tune.hosts": "Archivy:",
254
255
  "ui.tune.host": " {host}: sezení {sessions} · požadavky {requests} ({perSession} na sezení, {perImage} na snímek) · čekání {wait} min · limit vyčerpán {later}×",
255
256
  "ui.tune.web": "Webové nástroje agenta:",
@@ -754,6 +755,7 @@
754
755
  "ui.update.downloading": "Stahuji strom {version}…",
755
756
  "ui.update.failed": "Aktualizace se nepovedla: {detail}. strom zůstává, jak byl.",
756
757
  "ui.update.done": "✓ strom je aktualizovaný: {previous} → {version}.",
758
+ "ui.update.whatsnew": "Co je nového: {url}",
757
759
  "ui.update.restart": "strom je aktualizovaný — tahle nabídka ale ještě běží ve staré verzi. Po novém spuštění stromu (nebo zástupce „{shortcut}“) poběží nová verze.",
758
760
  "ui.o.update": "aktualizace",
759
761
  "ui.o.update.new": "vyšla {version} (tato je {current}) – řekni to uživateli; strom update ji s jeho souhlasem nainstaluje",
@@ -250,6 +250,7 @@
250
250
  "ui.tune.book.weak": "Negativbefunde auf schwachen Scans {n} von {of}",
251
251
  "ui.tune.book.requests": "Anfragen {n}",
252
252
  "ui.tune.book.unread": "geladen und nie gelesen {n} von {of}",
253
+ "ui.tune.book.background": "im Hintergrund: nie gelesen {n} von {of}",
253
254
  "ui.tune.hosts": "Archive:",
254
255
  "ui.tune.host": " {host}: Sitzungen {sessions} · Anfragen {requests} ({perSession} pro Sitzung, {perImage} pro Bild) · gewartet {wait} min · Limit erschöpft {later}×",
255
256
  "ui.tune.web": "Eigene Webwerkzeuge des Agenten:",
@@ -754,6 +755,7 @@
754
755
  "ui.update.downloading": "strom {version} wird heruntergeladen…",
755
756
  "ui.update.failed": "Die Aktualisierung hat nicht geklappt: {detail}. strom bleibt, wie er war.",
756
757
  "ui.update.done": "✓ strom ist aktualisiert: {previous} → {version}.",
758
+ "ui.update.whatsnew": "Was ist neu: {url}",
757
759
  "ui.update.restart": "strom ist aktualisiert — dieses Menü läuft aber noch in der alten Version. Nach einem Neustart von strom (oder der Verknüpfung „{shortcut}“) läuft die neue Version.",
758
760
  "ui.o.update": "Update",
759
761
  "ui.o.update.new": "{version} ist da (diese ist {current}) – sag es dem Benutzer; strom update installiert sie mit seiner Zustimmung",
@@ -102,6 +102,9 @@ conclusion so the next researcher can follow it.
102
102
  number by one or two images, never a batch of ten; an entry: its image ±1
103
103
  (it may run over a page). An archive that allows automation: batches as
104
104
  usual (about ±5 around the target, an index in batches) — fewer turns.
105
+ `strom read` may follow a fetch at work: sooner, never more images.
106
+ Images you will read later in the session: `--background` fetches them
107
+ while you work on, `strom read` reads them as they come.
105
108
  <!-- end -->
106
109
  - **What strom or the tree denies stays denied.** Never offer the user to
107
110
  change your permissions or settings, strom's or the tree's, to get round it:
@@ -65,6 +65,13 @@ never open again a view cleared from your context — your notes hold it.
65
65
  and headings only, one surname's entries, one entry whole — and transcribes
66
66
  nothing else. Record each report as it comes back, before the next readers.
67
67
  Only the entries that will be cited need your own eyes, at full resolution.
68
+ <!-- for link verify enrich -->
69
+ - **`--until-found` only for ONE particular entry** (one person's baptism,
70
+ marriage or burial whose details you know): no more readers begin once one
71
+ reports it. Never for every entry of a place or surname, nor where a
72
+ namesake may follow. Record only the images it names as read, `--result
73
+ partial`; if the find is not the one, read the rest.
74
+ <!-- end -->
68
75
  - **Report image by image, as you go**: the image and page, what was found (or
69
76
  nothing), what was illegible and where, the hand, how sure each name is.
70
77
  - **Cite the image, and where the entry is on it**: `strom source add …
@@ -39,6 +39,7 @@ import { ENRICH_PAGES, STORY_SOURCES_ORIGIN } from "../core/stories.js";
39
39
  import { sandboxOf, WITHOUT_READERS } from "../core/sandbox.js";
40
40
  import { WEB_SOFT } from "../core/metrics.js";
41
41
  import { indexBriefLines } from "../core/indexfirst.js";
42
+ import { activeFetches, endedFetches } from "../core/fetching.js";
42
43
  /** The hosts of a connector, as its manifest names them (a wildcard without its star). */
43
44
  function connectorHosts(shared, name) {
44
45
  try {
@@ -627,6 +628,15 @@ function imagesLines(tree, b, route, shape, finder, noSharper = false, waiting)
627
628
  : ""),
628
629
  ].filter(Boolean);
629
630
  }
631
+ /** The fetches at work for the research (each book: how far), and those in the background that ended lately with images left. */
632
+ function fetchingLines(root) {
633
+ const active = activeFetches(root);
634
+ const out = active.map((m) => `- ${m.recordset}: ${m.registered}/${m.images.length} images registered (${m.background ? `in the background${m.task ? `, for ${m.task}` : ""}` : "in the foreground"}) — strom read ${m.recordset} --images ${runs(m.images)} reads them as they come; not fetched again`);
635
+ for (const e of endedFetches(root))
636
+ if (e.missed.length && !active.some((m) => m.recordset === e.recordset))
637
+ out.push(`- ${e.recordset}: a fetch in the background ended with images ${runs(e.missed)} not fetched${e.stopped ? ` (stopped: ${e.stopped})` : ""} — strom fetch ${e.connector} ${shellArg(e.book)} --images ${runs(e.missed)} --recordset ${e.recordset} if they are needed`);
638
+ return out;
639
+ }
630
640
  /** What the user put in the shared inbox, waiting to be registered — by folder (one download each). */
631
641
  function inboxLines(shared) {
632
642
  if (!shared)
@@ -1197,6 +1207,15 @@ export function buildBrief(tree, opts) {
1197
1207
  ' strom media add --inbox "<folder>" --recordset B… · a document (certificate, letter): strom intake <file>',
1198
1208
  ].join("\n"),
1199
1209
  });
1210
+ // 7c. images being fetched for the research now (strom fetch --background of an earlier session, or another strom),
1211
+ // and what fetches in the background left unfetched lately: read as they come, never fetched twice
1212
+ const fetching = fetchingLines(tree.root);
1213
+ if (fetching.length)
1214
+ sections.push({
1215
+ name: "fetching",
1216
+ pointer: "strom fetch --status",
1217
+ text: ["## Images being fetched now (strom fetch --status)", ...fetching].join("\n"),
1218
+ });
1200
1219
  // 8. method for this kind of task
1201
1220
  // (budgeted first, with the record sets and the commands: when the brief is long, the lists are cut before how to work)
1202
1221
  // only the parts the level uses — of getting images, those of how this task's record sets get them (none named:
@@ -29,8 +29,8 @@ const RARE = {
29
29
  "task add": ["research", "anyway"],
30
30
  "media view": ["scale", "max", "grey", "png", "image"],
31
31
  read: ["context", "batch", "model", "minutes", "max"],
32
- // a book the research has already: the refusal says --again where it applies
33
- fetch: ["take", "result", "again"],
32
+ // a book the research has already: the refusal says --again where it applies; --status and --stop: the method and the brief name them
33
+ fetch: ["take", "result", "again", "status", "stop"],
34
34
  "source add": ["translation", "media"],
35
35
  "source edit": ["translation", "media"],
36
36
  "recordset add": ["images", "again"],
@@ -13,7 +13,9 @@ export function commitMessage(summaries, command) {
13
13
  /** Commit the changes of this command. Returns what blocked the commit, if anything did. */
14
14
  export function autoCommit(ctx, def) {
15
15
  const tree = ctx.current();
16
- if (!tree || tree.dryRun || tree.written.length === 0)
16
+ // what an earlier commit of the same command holds already (strom fetch commits each batch of images) is not again
17
+ const ops = tree?.uncommitted() ?? [];
18
+ if (!tree || tree.dryRun || ops.length === 0)
17
19
  return undefined;
18
20
  // Incremental gate: records were validated when written; here only what
19
21
  // changed since the last commit is checked, so the cost stays flat.
@@ -30,11 +32,11 @@ export function autoCommit(ctx, def) {
30
32
  problems: errors.map((f) => ({ ...(f.id ? { id: f.id } : {}), ...(f.file ? { file: f.file } : {}), message: f.message })),
31
33
  };
32
34
  }
33
- const message = commitMessage(tree.written.map((o) => o.summary), `strom ${def.path.join(" ")}`);
35
+ const message = commitMessage(ops.map((o) => o.summary), `strom ${def.path.join(" ")}`);
34
36
  // Only the files this command wrote, plus the free-form folders the agent
35
37
  // may edit — `git add` then never has to scan the whole data/ tree.
36
38
  const written = new Set(["data/_counters.json"]);
37
- for (const op of tree.written)
39
+ for (const op of ops)
38
40
  for (const f of op.files)
39
41
  written.add(f.path);
40
42
  for (const f of tree.opsFilesTouched())
@@ -43,6 +45,7 @@ export function autoCommit(ctx, def) {
43
45
  // commit of something else (found on Windows: strom mode committed a file of the user's) — nor by the switch itself
44
46
  const free = isArchive(tree) || def.path[0] === "mode" ? ["inputs", "output"] : ["notes", "tools", "inputs", "output"];
45
47
  tree.withTreeLock(() => tree.commit(message, [...written, ...free]));
48
+ tree.markCommitted();
46
49
  tree.settle();
47
50
  return undefined;
48
51
  }
@@ -346,9 +346,9 @@ export function severalRecords(def, args) {
346
346
  return RECORD_SHOWS.has(def.path.join(" ")) && args.length > 1 && args.every(isRecordRef);
347
347
  }
348
348
  /** Required and surplus positional arguments; `fix`: the line as it is meant (parseOptions), said under a surplus one. */
349
- export function checkArgs(def, args, fix, program = "strom") {
349
+ export function checkArgs(def, args, fix, program = "strom", opts = {}) {
350
350
  const declared = def.args ?? [];
351
- const required = declared.filter((a) => a.required);
351
+ const required = declared.filter((a) => a.required && !a.unless?.some((o) => opts[o] !== undefined && opts[o] !== false));
352
352
  if (args.length < required.length) {
353
353
  const missing = required[args.length];
354
354
  throw new UsageError(`missing <${missing.name}>: ${missing.description}`, { hint: `strom help ${def.path.join(" ")}`, code: "arg.missing", params: { arg: missing.name, cmd: def.path.join(" ") }, usage: usageOf(def) });
package/dist/cli/main.js CHANGED
@@ -23,7 +23,7 @@ import { prependPath } from "../runners/runner.js";
23
23
  import { shimDir } from "../commands/session.js";
24
24
  import { isAgent } from "../core/which.js";
25
25
  import { appUrlShown, noticeStromApp } from "../core/stromapp.js";
26
- import { lastChannelOf, pendingTransition, updateChannel } from "../core/update.js";
26
+ import { lastChannelOf, pendingTransition, updateChannel, whatsNewUrl } from "../core/update.js";
27
27
  import { backupBefore, backupSaid } from "./backups.js";
28
28
  import { guestOf, installation, ownFolder, refreshTreeShim } from "../core/self.js";
29
29
  import { refreshGlobal } from "../agents/global.js";
@@ -133,6 +133,21 @@ function asOwnCommand(io, name, json, argv) {
133
133
  const text = (s) => asCommand(s, name, words);
134
134
  return { ...io, stdout: (s) => io.stdout(json ? asCommandJson(s, name, words) : text(s)), stderr: (s) => io.stderr(text(s)) };
135
135
  }
136
+ /**
137
+ * strom --version: the version on its first line (what a script reads), then where to read what is new in it — the
138
+ * changelog in the research language, on the branch of the installation's channel. --json gives the version alone.
139
+ */
140
+ function versionSaid(env, cwd, io, values = {}) {
141
+ const first = `strom ${VERSION}\n`;
142
+ try {
143
+ const lang = Context.fromOptions({ env, cwd, io, json: false, values }).uiLang();
144
+ return `${first}${ui(lang, "ui.update.whatsnew", { url: whatsNewUrl(VERSION, updateChannel(env), lang) })}\n`;
145
+ }
146
+ catch {
147
+ // no settings to read: the version alone
148
+ return first;
149
+ }
150
+ }
136
151
  export async function main(argv, io, env, cwd) {
137
152
  const json = argv.includes("--json");
138
153
  const own = ownCommand(env);
@@ -157,7 +172,7 @@ export async function main(argv, io, env, cwd) {
157
172
  if (argv.length === 0 && io.tty && !isAgent(env) && env.STROM_NONINTERACTIVE !== "1")
158
173
  argv = ["menu"];
159
174
  if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v")) {
160
- io.stdout(json ? toJson({ version: VERSION }) : `strom ${VERSION}\n`);
175
+ io.stdout(json ? toJson({ version: VERSION }) : versionSaid(env, cwd, io));
161
176
  return EXIT.ok;
162
177
  }
163
178
  let resolved;
@@ -185,7 +200,7 @@ export async function main(argv, io, env, cwd) {
185
200
  return EXIT.ok;
186
201
  }
187
202
  if (v.version) {
188
- io.stdout(json ? toJson({ version: VERSION }) : `strom ${VERSION}\n`);
203
+ io.stdout(json ? toJson({ version: VERSION }) : versionSaid(env, cwd, io, v));
189
204
  return EXIT.ok;
190
205
  }
191
206
  const ctx = Context.fromOptions({ env, cwd, io, json, values: v });
@@ -337,13 +352,14 @@ export async function main(argv, io, env, cwd) {
337
352
  // several IDs to a show command of one record (person show P0001 P0002): each shown in turn, as strom show does
338
353
  const shows = severalRecords(def, args) ? match(["show"])?.def : undefined;
339
354
  if (!shows)
340
- checkArgs(def, args, parsed.fix, own);
355
+ checkArgs(def, args, parsed.fix, own, parsed.values);
341
356
  if (def.writes && v["dry-run"])
342
357
  ctx.dryRun = true;
343
358
  const work = async () => {
344
359
  // Never write on top of data that was changed outside strom.
360
+ // (a command that locks only while it writes looks under the lock too: another strom may be in the middle of a write)
345
361
  if (def.writes && def.tree)
346
- assertIntact(ctx.tree());
362
+ def.lock === "sections" ? ctx.tree().withTreeLock(() => assertIntact(ctx.tree())) : assertIntact(ctx.tree());
347
363
  let result;
348
364
  try {
349
365
  result = await (shows ?? def).run(ctx, { args, opts: v, ...(passthrough.length ? { extra: passthrough } : {}), ...(parsed.seq ? { seq: parsed.seq } : {}) });
package/dist/cli/ui.js CHANGED
@@ -227,6 +227,7 @@ export const UI = {
227
227
  "ui.tune.book.weak": "negatives on weak scans {n} of {of}",
228
228
  "ui.tune.book.requests": "requests {n}",
229
229
  "ui.tune.book.unread": "fetched and never read {n} of {of}",
230
+ "ui.tune.book.background": "in the background: never read {n} of {of}",
230
231
  "ui.tune.hosts": "Archives:",
231
232
  "ui.tune.host": " {host}: sessions {sessions} · requests {requests} ({perSession} per session, {perImage} per image) · waited {wait} min · limit used up {later}×",
232
233
  "ui.tune.web": "The agent's own web tools:",
@@ -681,6 +682,7 @@ export const UI = {
681
682
  "ui.update.downloading": "Downloading strom {version}…",
682
683
  "ui.update.failed": "The update did not work: {detail}. strom stays as it was.",
683
684
  "ui.update.done": "✓ strom is updated: {previous} → {version}.",
685
+ "ui.update.whatsnew": "What's new: {url}",
684
686
  "ui.update.restart": "strom is updated — this menu still runs the old version. Start strom again (or the shortcut \"{shortcut}\"), and the new version is at work.",
685
687
  "ui.o.update": "update",
686
688
  "ui.o.update.new": "{version} is out (this is {current}) — tell the user; strom update installs it with their yes",
@@ -217,7 +217,7 @@ register({
217
217
  // one line cannot be a dry run while the others write: the whole batch is one or neither
218
218
  if (parsed.values["dry-run"])
219
219
  throw new UsageError("--dry-run applies to the whole batch: strom batch --dry-run …");
220
- checkArgs(def, parsed.positionals, parsed.fix, "");
220
+ checkArgs(def, parsed.positionals, parsed.fix, "", parsed.values);
221
221
  const before = tree.written.length;
222
222
  const r = await def.run(ctx, { args: parsed.positionals, opts: parsed.values });
223
223
  if (r.exitCode)
@@ -164,6 +164,7 @@ function report(ctx, tree, askedKey, whoAsked) {
164
164
  b.weakNegatives ? t("ui.tune.book.weak", { n: b.weakNegatives, of: b.negatives }) : undefined,
165
165
  b.requests ? t("ui.tune.book.requests", { n: b.requests }) : undefined,
166
166
  b.unread ? t("ui.tune.book.unread", { n: b.unread, of: b.fetched }) : undefined,
167
+ b.background ? t("ui.tune.book.background", { n: b.background.unread, of: b.background.fetched }) : undefined,
167
168
  ]
168
169
  .filter(Boolean)
169
170
  .join(" · "));