strom-research 1.25.1-beta.1 → 1.25.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.cs.md +29 -1
- package/CHANGELOG.de.md +38 -1
- package/CHANGELOG.md +31 -1
- package/CHANGELOG.pl.md +33 -1
- package/assets/lang/cs.json +7 -0
- package/assets/lang/de.json +7 -0
- package/assets/lang/pl.json +7 -0
- package/assets/plugins/gates/claude-usage/gate.json +1 -1
- package/assets/plugins/gates/claude-usage/gate.ts +174 -24
- package/assets/terms/streets.json +214 -20
- package/dist/brief/places.js +40 -5
- package/dist/cli/ui.js +1 -0
- package/dist/commands/browse.js +11 -1
- package/dist/commands/history.js +4 -2
- package/dist/commands/placeid.js +100 -23
- package/dist/commands/session.js +10 -2
- package/dist/commands/sources.js +5 -13
- package/dist/commands/start.js +5 -2
- package/dist/commands/sync.js +2 -2
- package/dist/core/claudedir.js +59 -3
- package/dist/core/gate.js +3 -3
- package/dist/core/people.js +1 -1
- package/dist/core/phrases.js +6 -0
- package/dist/core/placeid.js +733 -79
- package/dist/core/placesview.js +1 -0
- package/dist/core/schema.js +3 -0
- package/dist/core/session.js +5 -0
- package/dist/core/streetfix.js +24 -1
- package/dist/core/streets.js +64 -5
- package/dist/core/sync.js +24 -13
- package/dist/core/tree.js +1 -1
- package/dist/runners/runner.js +3 -3
- package/package.json +1 -4
package/CHANGELOG.cs.md
CHANGED
|
@@ -5,9 +5,37 @@
|
|
|
5
5
|
Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou na
|
|
6
6
|
[GitHub Releases](https://github.com/ACiDekCZ/strom-research/releases) (anglicky).
|
|
7
7
|
|
|
8
|
+
## 1.25.2
|
|
9
|
+
|
|
10
|
+
*3. 11. 2026 · patří k aplikaci Strom 3.21.2.*
|
|
11
|
+
|
|
12
|
+
- **Brána týdne předplatného už neukončí běh kvůli jedné nepřečtené odpovědi.** Zeptá se znovu, pak čtvrt hodiny počká
|
|
13
|
+
a řekne, co dostala. Nepřihlášený agent se ohlásí už před prvním sezením i s postupem přihlášení; výchozí složka
|
|
14
|
+
agenta zadaná v prostředí se vynechá, takže agent zůstane u svého obvyklého přihlášení.
|
|
15
|
+
- **Ztotožnění míst: kontrola zapíše, co zjistí, do záznamu místa.** Staré názvy a pravopisy, jiné tvary místa
|
|
16
|
+
v údajích a chybná fara (odebraná i s důvodem) se zapíší do záznamu i tam, kde souřadnice zadala osoba, nikdy jen
|
|
17
|
+
do dějin. Tvar blízko místa, které záznam už má, se odmítne a řekne se, jak ho k tomu záznamu přidat; dva záznamy
|
|
18
|
+
jednoho místa jde sloučit. Na prvním místě je dnešní název (místo dnes v Polsku polsky, německý název jako
|
|
19
|
+
historický). Otázka u místa nikdy nebere osobě její souřadnice. Už ztotožněná místa se jednou samy srovnají (názvy
|
|
20
|
+
zapsané i s jazykem a roky jako text, dnešní název první, souřadnice osoby, staré názvy jen v dějinách) a krok jde
|
|
21
|
+
vrátit. Souhrn sezení o místech začíná tím, co se udělalo – ztotožněno, ověřeno, s otázkou, nedohledáno – a jedno
|
|
22
|
+
sezení vezme víc míst sousedních oblastí. Z „v Lhotě v čp. 12“ už nezůstane místo „v Lhotě v“:
|
|
23
|
+
místem je obec, jak ji výzkum píše jinde, i když ji věta skloňuje. Každý název se do záznamu místa zapíše jen jednou.
|
|
24
|
+
- **Fronta míst se po vyřízení míst vyprázdní.** Místo ztotožněné, ověřené, předané k otázce nebo nedohledané odejde z
|
|
25
|
+
fronty se všemi svými tvary, i s tím, který fakta mezitím píší jinak; místa, která sezení už udělalo, se znovu neberou
|
|
26
|
+
a počet čekajících sedí. Co ve frontě nechala dřívější verze, se odebere samo (jen vyřízená místa, krok jde vrátit);
|
|
27
|
+
kontrola vyžádaná u místa, které čeká na ztotožnění, zůstane jako kontrola, a kontrola čekající u tvaru, který fakta
|
|
28
|
+
nově píší jako jiné místo, přejde s nimi k tomu místu. Každý krok nad místy jde vrátit vlastním
|
|
29
|
+
číslem, i když čeká jiný (také odpovědi člověka). Chybné jméno v záznamu místa — překlep ze záznamu, který už žádný
|
|
30
|
+
fakt nepíše, jméno jiného místa — jde odebrat i s důvodem (`strom place names L… --drop`); sezení nad místy vidí další
|
|
31
|
+
jména záznamu s jazykem a roky; kontrola může uvést, o jaký zdroj se opřela; příslušnost jde zadat i k jednomu roku.
|
|
32
|
+
- V rozporech úpravy z aplikace už číslo domu, které značku má, nedostane značku podruhé (polsky „nr domu nr domu 12“),
|
|
33
|
+
a pomlčka v titulku je podle jazyka výzkumu.
|
|
34
|
+
- Plán toho, co agent udělá dál, už nespadne na čekajícím vyprávění o manželích: jmenuje oba manžele.
|
|
35
|
+
|
|
8
36
|
## 1.25.1
|
|
9
37
|
|
|
10
|
-
*
|
|
38
|
+
*3. 11. 2026 · patří k aplikaci Strom 3.21.1.*
|
|
11
39
|
|
|
12
40
|
- **Podmínky archivů ověřené znovu, přibyly polské zdroje.** Podmínky a robots.txt všech archivů, které strom zná,
|
|
13
41
|
byly 3. listopadu 2026 přečteny znovu. eBadatelna SOA Praha nově nepouští k digitalizátům žádné roboty (její
|
package/CHANGELOG.de.md
CHANGED
|
@@ -5,9 +5,46 @@
|
|
|
5
5
|
Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Installationszeilen stehen bei
|
|
6
6
|
[GitHub Releases](https://github.com/ACiDekCZ/strom-research/releases) (auf Englisch).
|
|
7
7
|
|
|
8
|
+
## 1.25.2
|
|
9
|
+
|
|
10
|
+
*3.11.2026 · gehört zur Strom-App 3.21.2.*
|
|
11
|
+
|
|
12
|
+
- **Das Tor der Abo-Woche beendet einen Lauf nicht mehr wegen einer unlesbaren Antwort.** Es fragt erneut, wartet dann
|
|
13
|
+
eine Viertelstunde und nennt, was es erhielt. Ein nicht angemeldeter Agent wird schon vor der ersten Sitzung gemeldet,
|
|
14
|
+
mit dem Weg zur Anmeldung; der in der Umgebung genannte Standardordner des Agenten wird weggelassen, so bleibt der
|
|
15
|
+
Agent bei seiner üblichen Anmeldung.
|
|
16
|
+
- **Orte zuordnen: eine Prüfung schreibt, was sie findet, in den Datensatz des Ortes.** Alte Namen und Schreibweisen,
|
|
17
|
+
andere Formen des Ortes in den Angaben und eine falsche Pfarrei (mit Grund entfernt) kommen in den Datensatz, auch
|
|
18
|
+
wo die Koordinaten von der Person stammen, nie nur in die Geschichte. Eine Form nahe einem Ort, der schon einen
|
|
19
|
+
Datensatz hat, wird abgelehnt, mit dem Weg, sie diesem Datensatz hinzuzufügen; zwei Datensätze eines Ortes lassen sich
|
|
20
|
+
zusammenführen. Der heutige Name steht vorn (ein Ort heute in Polen polnisch, der deutsche Name als historischer).
|
|
21
|
+
Eine Frage zu einem Ort nimmt der Person nie ihre Koordinaten. Schon zugeordnete Orte werden einmal von selbst
|
|
22
|
+
berichtigt (Namen mit Sprache und Jahren als Text, der heutige Name vorn, die Koordinaten der Person, alte Namen nur
|
|
23
|
+
in der Geschichte), und der Schritt lässt sich zurücknehmen. Die Zusammenfassung einer Sitzung zu Orten beginnt mit
|
|
24
|
+
dem, was getan wurde – zugeordnet, bestätigt, mit Frage, nicht gefunden –, und eine Sitzung nimmt mehr Orte
|
|
25
|
+
benachbarter Gebiete. Aus „in Lhota in Nr. 12“ bleibt kein Ort „in Lhota in“ mehr:
|
|
26
|
+
Ort ist die Gemeinde, wie die Forschung sie sonst schreibt, auch wenn der Satz sie beugt. Jeder Name steht nur einmal
|
|
27
|
+
im Eintrag eines Ortes.
|
|
28
|
+
- **Die Warteschlange der Orte leert sich, wenn die Orte erledigt sind.** Ein Ort, der bestimmt, geprüft, erfragt oder
|
|
29
|
+
nicht gefunden wurde, verlässt die Warteschlange mit allen seinen Formen, auch mit einer, die die Fakten inzwischen
|
|
30
|
+
anders schreiben; Orte, die eine Sitzung schon erledigt hat, werden nicht erneut genommen, und die Zahl des Wartenden
|
|
31
|
+
stimmt. Was eine frühere Version in der Warteschlange ließ, wird von selbst entfernt (nur erledigte Orte, der Schritt
|
|
32
|
+
lässt sich rückgängig machen); eine Prüfung, die für einen Ort verlangt wird, der auf die Bestimmung wartet, bleibt
|
|
33
|
+
als Prüfung, und eine Prüfung, die bei einer Form wartet, die die Fakten jetzt als anderen Ort schreiben, geht mit
|
|
34
|
+
ihnen zu diesem Ort. Jeder Schritt bei den Orten lässt sich mit seiner eigenen Nummer rückgängig machen, auch wenn ein anderer
|
|
35
|
+
wartet (auch die Antworten der Person). Ein falscher Name im Datensatz eines Ortes — ein Versehen eines Eintrags, das
|
|
36
|
+
kein Faktum mehr schreibt, der Name eines anderen Ortes — lässt sich mit Begründung entfernen (`strom place names L…
|
|
37
|
+
--drop`); die Sitzung über die Orte sieht die weiteren Namen des Datensatzes mit Sprache und Jahren; eine Prüfung kann
|
|
38
|
+
die Quelle nennen, auf die sie sich stützt; eine Zugehörigkeit lässt sich auch für ein einzelnes Jahr angeben.
|
|
39
|
+
- In den Widersprüchen einer Änderung aus der App bekommt eine Hausnummer, die ihre Bezeichnung schon trägt, sie nicht
|
|
40
|
+
ein zweites Mal (polnisch „nr domu nr domu 12“), und der Gedankenstrich im Titel richtet sich nach der Sprache der
|
|
41
|
+
Forschung.
|
|
42
|
+
- Der Plan dessen, was der Agent als Nächstes tut, bricht bei einer wartenden Geschichte eines Paares nicht mehr ab:
|
|
43
|
+
er nennt beide Partner.
|
|
44
|
+
|
|
8
45
|
## 1.25.1
|
|
9
46
|
|
|
10
|
-
*
|
|
47
|
+
*3.11.2026 · gehört zur Strom-App 3.21.1.*
|
|
11
48
|
|
|
12
49
|
- **Die Bedingungen der Archive erneut geprüft, polnische Quellen dazu.** Bedingungen und robots.txt jedes Archivs,
|
|
13
50
|
das strom kennt, wurden am 3. November 2026 neu gelesen. Die eBadatelna des SOA Praha lässt jetzt keinen Roboter
|
package/CHANGELOG.md
CHANGED
|
@@ -5,9 +5,39 @@ English · [Čeština](CHANGELOG.cs.md) · [Deutsch](CHANGELOG.de.md) · [Polski
|
|
|
5
5
|
The changes of each version in short. The full notes and the install lines are on
|
|
6
6
|
[GitHub Releases](https://github.com/ACiDekCZ/strom-research/releases).
|
|
7
7
|
|
|
8
|
+
## 1.25.2
|
|
9
|
+
|
|
10
|
+
*2026-11-03 · goes with the Strom app 3.21.2.*
|
|
11
|
+
|
|
12
|
+
- **The gate of the subscription's week no longer ends a run over one answer it cannot read.** It asks again, then
|
|
13
|
+
waits a quarter of an hour and says what it got. An agent not logged in is said before the first session, with how to
|
|
14
|
+
log in; the agent's default folder named in the environment is left out, so the agent keeps its usual login.
|
|
15
|
+
- **Identifying places: a check writes what it finds into the place's record.** Old names and spellings, the facts'
|
|
16
|
+
other forms of the place and a wrong parish (taken off with the reason) go into the record also where the coordinates
|
|
17
|
+
are the person's, never only into the history. A form close to a place with a record already is refused with the way
|
|
18
|
+
to add it to that record; two records of one place can be merged. Today's name comes first (a place now in Poland
|
|
19
|
+
under its Polish name, the German one as historical). A question on a place never takes the person's coordinates
|
|
20
|
+
away from them. The places identified before are set right once by themselves (names written with their language
|
|
21
|
+
and years as text, today's name first, the person's coordinates, old names kept only in the history), and the step
|
|
22
|
+
can be undone. A session on the places begins its summary with what it did — identified, verified, asked, not
|
|
23
|
+
located — and takes more places of neighbouring areas at once. "in Lhota in no. 12" no longer leaves "in Lhota in" as
|
|
24
|
+
a place: the place is the municipality as the research writes it elsewhere, also where the sentence declined it. Each
|
|
25
|
+
name goes into a place's record once.
|
|
26
|
+
- **The queue of places empties as the places are done.** A place identified, checked, asked about or not located leaves
|
|
27
|
+
the queue with every form of it, also one the facts write otherwise since; the places a session already did are no
|
|
28
|
+
longer taken again, and the count of what waits is right. What an earlier version left in the queue is taken out by
|
|
29
|
+
itself (only places dealt with, the step can be undone); a check asked of a place waiting to be identified is kept as
|
|
30
|
+
the check, and a check waiting for a form the facts now write as another place goes with them to that place. Each step on places is undone by its own number, also while another waits (the person's answers among
|
|
31
|
+
them). A wrong name of a place's record — a slip of a record no fact writes any more, another place's name — can be
|
|
32
|
+
taken off with the reason (`strom place names L… --drop`); the session on the places sees the record's other names
|
|
33
|
+
with their language and years; a check can name the reference it stood on; a jurisdiction can be given for one year.
|
|
34
|
+
- In the conflicts of an edit from the app a house that says it is one already no longer gets its marker twice ("nr domu nr
|
|
35
|
+
domu 12" in Polish), and the title's dash is the one of the research's language.
|
|
36
|
+
- The plan of what the agent does next no longer fails on a couple's story waiting: it names the two partners.
|
|
37
|
+
|
|
8
38
|
## 1.25.1
|
|
9
39
|
|
|
10
|
-
*
|
|
40
|
+
*2026-11-03 · goes with the Strom app 3.21.1.*
|
|
11
41
|
|
|
12
42
|
- **The archives' terms checked again, Polish sources added.** The terms and robots.txt of every archive strom knows
|
|
13
43
|
were read again on 3 November 2026. SOA Praha's eBadatelna now keeps every robot off its scans (its robots.txt of
|
package/CHANGELOG.pl.md
CHANGED
|
@@ -5,9 +5,41 @@
|
|
|
5
5
|
Zmiany każdej wersji w skrócie. Pełne informacje o wydaniach i polecenia instalacji są na
|
|
6
6
|
[GitHub Releases](https://github.com/ACiDekCZ/strom-research/releases) (po angielsku).
|
|
7
7
|
|
|
8
|
+
## 1.25.2
|
|
9
|
+
|
|
10
|
+
*3.11.2026 · współpracuje z aplikacją Strom 3.21.2.*
|
|
11
|
+
|
|
12
|
+
- **Warunek tygodnia subskrypcji nie kończy już przebiegu z powodu jednej nieodczytanej odpowiedzi.** Pyta ponownie,
|
|
13
|
+
potem czeka kwadrans i podaje, co otrzymał. Niezalogowany agent jest zgłaszany już przed pierwszą sesją, ze sposobem
|
|
14
|
+
logowania; domyślny folder agenta podany w środowisku jest pomijany, więc agent zostaje przy swoim zwykłym logowaniu.
|
|
15
|
+
- **Identyfikacja miejsc: sprawdzenie zapisuje to, co ustali, w rekordzie miejsca.** Dawne nazwy i pisownie, inne formy
|
|
16
|
+
miejsca w danych i błędna parafia (usunięta wraz z powodem) trafiają do rekordu także tam, gdzie współrzędne podała
|
|
17
|
+
osoba, nigdy tylko do historii. Forma blisko miejsca, które ma już rekord, zostaje odrzucona ze wskazaniem, jak ją
|
|
18
|
+
do tego rekordu dodać; dwa rekordy jednego miejsca można scalić. Na pierwszym miejscu jest dzisiejsza nazwa
|
|
19
|
+
(miejscowość dziś w Polsce po polsku, niemiecka nazwa jako historyczna). Pytanie o miejsce nigdy nie odbiera osobie
|
|
20
|
+
jej współrzędnych. Miejsca już zidentyfikowane zostają raz same poprawione (nazwy zapisane z językiem i latami jako
|
|
21
|
+
tekst, dzisiejsza nazwa pierwsza, współrzędne osoby, dawne nazwy tylko w historii), a krok można cofnąć.
|
|
22
|
+
Podsumowanie sesji o miejscach zaczyna się od tego, co zrobiono – zidentyfikowano, potwierdzono, z pytaniem, nie
|
|
23
|
+
odnaleziono – a jedna sesja bierze więcej miejsc z sąsiednich obszarów. Z „w Lhocie w nr 12” nie zostaje już
|
|
24
|
+
miejsce „w Lhocie w”: miejscem jest miejscowość tak, jak badanie zapisuje ją gdzie indziej, także gdy zdanie ją
|
|
25
|
+
odmienia. Każda nazwa trafia do rekordu miejsca tylko raz.
|
|
26
|
+
- **Kolejka miejsc opróżnia się, gdy miejsca są załatwione.** Miejsce zidentyfikowane, sprawdzone, przekazane jako
|
|
27
|
+
pytanie albo nieustalone opuszcza kolejkę ze wszystkimi swoimi formami, także z tą, którą fakty zapisują teraz
|
|
28
|
+
inaczej; miejsca, które sesja już zrobiła, nie są brane ponownie, a liczba oczekujących się zgadza. To, co w kolejce
|
|
29
|
+
zostawiła wcześniejsza wersja, usuwa się samo (tylko załatwione miejsca, krok można cofnąć); sprawdzenie zlecone dla
|
|
30
|
+
miejsca czekającego na identyfikację zostaje jako sprawdzenie, a sprawdzenie czekające przy formie, którą fakty
|
|
31
|
+
zapisują teraz jako inne miejsce, przechodzi z nimi do tego miejsca. Każdy krok przy miejscach można cofnąć jego własnym
|
|
32
|
+
numerem, także gdy czeka inny (również odpowiedzi osoby). Błędną nazwę w rekordzie miejsca — pomyłkę z zapisu, której
|
|
33
|
+
żaden fakt już nie zapisuje, nazwę innego miejsca — można usunąć z uzasadnieniem (`strom place names L… --drop`);
|
|
34
|
+
sesja nad miejscami widzi pozostałe nazwy rekordu z językiem i latami; sprawdzenie może podać źródło, na którym się
|
|
35
|
+
oparło; przynależność można podać także dla jednego roku.
|
|
36
|
+
- W sprzecznościach zmian z aplikacji numer domu, który ma już oznaczenie, nie dostaje go po raz drugi („nr domu nr
|
|
37
|
+
domu 12”), a myślnik w tytule zależy od języka badania.
|
|
38
|
+
- Plan tego, co agent zrobi dalej, nie przerywa się już na oczekującej opowieści o parze: podaje oboje partnerów.
|
|
39
|
+
|
|
8
40
|
## 1.25.1
|
|
9
41
|
|
|
10
|
-
*
|
|
42
|
+
*3.11.2026 · współpracuje z aplikacją Strom 3.21.1.*
|
|
11
43
|
|
|
12
44
|
- **Warunki archiwów sprawdzone ponownie, doszły źródła polskie.** Warunki i robots.txt każdego archiwum, które zna
|
|
13
45
|
strom, zostały 3 listopada 2026 przeczytane na nowo. eBadatelna SOA Praha nie wpuszcza już żadnych robotów do swoich
|
package/assets/lang/cs.json
CHANGED
|
@@ -809,6 +809,12 @@
|
|
|
809
809
|
"places.identify.why": "Osoba o to požádala: která obec toho jména to je, kde leží, její názvy a příslušnost – pro mapu, dějiny a domy.",
|
|
810
810
|
"places.identify.done": "Každé místo fronty ztotožněné, s otázkou, nebo nedohledané.",
|
|
811
811
|
"places.identify.result": "každé místo fronty ztotožněné, s otázkou, nebo nedohledané",
|
|
812
|
+
"places.count.identified": "ztotožněno {n}",
|
|
813
|
+
"places.count.verified": "ověřeno {n} (už ztotožněná, zkontrolováno)",
|
|
814
|
+
"places.count.undecided": "zkontrolováno, nerozhodnuto {n}",
|
|
815
|
+
"places.count.asked": "s otázkou {n}",
|
|
816
|
+
"places.count.unlocated": "nedohledáno {n}",
|
|
817
|
+
"places.count.merged": "sloučeno {n}",
|
|
812
818
|
"history.region.what": "Události dějin v regionu {region}: války, epidemie, sčítání lidu, změny státu, vystěhovalectví",
|
|
813
819
|
"history.region.where": "encyklopedie, státní instituce, archivy, Wikipedie v jazyce regionu s jejími odkazy",
|
|
814
820
|
"history.region.why": "co předkové zažili, ukázané u jejich životů",
|
|
@@ -2628,6 +2634,7 @@
|
|
|
2628
2634
|
"ui.conflict.titleAfter": "titul za jménem",
|
|
2629
2635
|
"ui.conflict.parents": "rodiče",
|
|
2630
2636
|
"ui.conflict.removed": "ve stromu",
|
|
2637
|
+
"ui.conflict.title": "{who}: {what} – {ours} × {theirs}",
|
|
2631
2638
|
"ui.conflict.removed.ours": "odebrání v aplikaci Strom ({day}, {sync})",
|
|
2632
2639
|
"ui.conflict.removed.theirs": "úpravy v jiné kopii aplikace",
|
|
2633
2640
|
"ui.conflict.removing.ours": "pozdější úpravy v jiné kopii aplikace",
|
package/assets/lang/de.json
CHANGED
|
@@ -809,6 +809,12 @@
|
|
|
809
809
|
"places.identify.why": "Die Person hat darum gebeten: welcher Ort des Namens es ist, wo er liegt, seine Namen und Zugehörigkeiten – für die Karte, die Geschichte und die Häuser.",
|
|
810
810
|
"places.identify.done": "Jeder Ort der Warteschlange bestimmt, als Frage gestellt oder als nicht auffindbar vermerkt.",
|
|
811
811
|
"places.identify.result": "jeder Ort der Warteschlange bestimmt, als Frage gestellt oder als nicht auffindbar vermerkt",
|
|
812
|
+
"places.count.identified": "zugeordnet {n}",
|
|
813
|
+
"places.count.verified": "bestätigt {n} (schon zugeordnet, geprüft)",
|
|
814
|
+
"places.count.undecided": "geprüft, nicht entschieden {n}",
|
|
815
|
+
"places.count.asked": "mit Frage {n}",
|
|
816
|
+
"places.count.unlocated": "nicht gefunden {n}",
|
|
817
|
+
"places.count.merged": "zusammengeführt {n}",
|
|
812
818
|
"history.region.what": "Ereignisse der Geschichte in {region}: Kriege, Epidemien, Volkszählungen, Wechsel des Staates, Auswanderung",
|
|
813
819
|
"history.region.where": "Enzyklopädien, staatliche Institutionen, Archive, die Wikipedia in der Sprache der Region mit ihren Belegen",
|
|
814
820
|
"history.region.why": "was die Vorfahren erlebten, bei ihren Lebensläufen gezeigt",
|
|
@@ -2628,6 +2634,7 @@
|
|
|
2628
2634
|
"ui.conflict.titleAfter": "Titel nach dem Namen",
|
|
2629
2635
|
"ui.conflict.parents": "Eltern",
|
|
2630
2636
|
"ui.conflict.removed": "im Stammbaum",
|
|
2637
|
+
"ui.conflict.title": "{who}: {what} – {ours} × {theirs}",
|
|
2631
2638
|
"ui.conflict.removed.ours": "in der Strom-App entfernt ({day}, {sync})",
|
|
2632
2639
|
"ui.conflict.removed.theirs": "in einer anderen Kopie der App bearbeitet",
|
|
2633
2640
|
"ui.conflict.removing.ours": "später in einer anderen Kopie der App bearbeitet",
|
package/assets/lang/pl.json
CHANGED
|
@@ -809,6 +809,12 @@
|
|
|
809
809
|
"places.identify.why": "Osoba o to poprosiła: która miejscowość o tej nazwie to jest, gdzie leży, jej nazwy i przynależność administracyjna – dla mapy, historii i domów.",
|
|
810
810
|
"places.identify.done": "Każde miejsce z kolejki zidentyfikowane, z pytaniem albo nieodnalezione.",
|
|
811
811
|
"places.identify.result": "każde miejsce z kolejki zidentyfikowane, z pytaniem albo nieodnalezione",
|
|
812
|
+
"places.count.identified": "zidentyfikowano {n}",
|
|
813
|
+
"places.count.verified": "potwierdzono {n} (już zidentyfikowane, sprawdzone)",
|
|
814
|
+
"places.count.undecided": "sprawdzono, nierozstrzygnięte {n}",
|
|
815
|
+
"places.count.asked": "z pytaniem {n}",
|
|
816
|
+
"places.count.unlocated": "nie odnaleziono {n}",
|
|
817
|
+
"places.count.merged": "scalono {n}",
|
|
812
818
|
"history.region.what": "Wydarzenia historyczne w regionie {region}: wojny, epidemie, spisy ludności, zmiany państwa, emigracja",
|
|
813
819
|
"history.region.where": "encyklopedie, instytucje państwowe, archiwa, Wikipedia w języku regionu wraz z jej przypisami",
|
|
814
820
|
"history.region.why": "co przeżyli przodkowie, pokazane przy ich życiorysach",
|
|
@@ -2628,6 +2634,7 @@
|
|
|
2628
2634
|
"ui.conflict.titleAfter": "tytuł po nazwisku",
|
|
2629
2635
|
"ui.conflict.parents": "rodzice",
|
|
2630
2636
|
"ui.conflict.removed": "w drzewie",
|
|
2637
|
+
"ui.conflict.title": "{who}: {what} – {ours} × {theirs}",
|
|
2631
2638
|
"ui.conflict.removed.ours": "usunięcie w aplikacji Strom ({day}, {sync})",
|
|
2632
2639
|
"ui.conflict.removed.theirs": "zmiany w innej kopii aplikacji",
|
|
2633
2640
|
"ui.conflict.removing.ours": "późniejsze zmiany w innej kopii aplikacji",
|
|
@@ -35,6 +35,13 @@
|
|
|
35
35
|
// limits in this run (STROM_AGENT_LIMITS, at most 15 minutes old) is read
|
|
36
36
|
// instead of asking `claude -p /usage`.
|
|
37
37
|
//
|
|
38
|
+
// What `claude -p /usage` says is not always read: an answer without the
|
|
39
|
+
// week's line, an exit status, no answer in time. It asks again (ATTEMPTS, a
|
|
40
|
+
// pause between), and then waits a while (UNREAD_WAIT) rather than stopping
|
|
41
|
+
// the run for good — what it got is in its reason (the first line, shortened).
|
|
42
|
+
// A Claude Code not logged in (`claude auth status`; its /usage is then a bare
|
|
43
|
+
// /cost) stops the run, saying how to log in.
|
|
44
|
+
//
|
|
38
45
|
// strom writes this file; a copy of your own goes into a folder of its own name.
|
|
39
46
|
|
|
40
47
|
import fs from "node:fs";
|
|
@@ -65,6 +72,18 @@ const RESERVE = 2;
|
|
|
65
72
|
/** The week it waits in for the reserve, in the gate's own folder — only when strom asks it (STROM_GATE). */
|
|
66
73
|
const HOLD = process.env.STROM_GATE ? "hold.json" : undefined;
|
|
67
74
|
const lang = (process.env.STROM_LANG ?? "en").slice(0, 2);
|
|
75
|
+
/**
|
|
76
|
+
* How many times `claude -p /usage` is asked before the gate waits; each asking may take ASK_MS — it reads the
|
|
77
|
+
* computer's own sessions for its advice too, which took over a minute on a busy computer (2026-11-03: 75 s).
|
|
78
|
+
*/
|
|
79
|
+
const ATTEMPTS = 3;
|
|
80
|
+
const ASK_MS = 180_000;
|
|
81
|
+
/** The pause between two askings (tests give a shorter one: CLAUDE_USAGE_RETRY_MS). */
|
|
82
|
+
const RETRY_MS = /^\d+$/.test(process.env.CLAUDE_USAGE_RETRY_MS ?? "") ? Number(process.env.CLAUDE_USAGE_RETRY_MS) : 30_000;
|
|
83
|
+
/** All the askings within this, inside the gate's timeout (gate.json: 660 s). */
|
|
84
|
+
const BUDGET_MS = 600_000;
|
|
85
|
+
/** Unread after every asking: asked again this much later — a run is not stopped for one answer it could not read. */
|
|
86
|
+
const UNREAD_WAIT = 15 * 60_000;
|
|
68
87
|
|
|
69
88
|
const T: Record<string, Record<string, string>> = {
|
|
70
89
|
en: {
|
|
@@ -84,8 +103,15 @@ const T: Record<string, Record<string, string>> = {
|
|
|
84
103
|
badcap: "the cap must be 1 to 100 (\"--cap 95\"), not \"{arg}\"",
|
|
85
104
|
cap: "week {used} % used — the cap of {cap} % reached; it resets {resets}",
|
|
86
105
|
unread: "cannot read `claude -p /usage`: {detail}",
|
|
106
|
+
unreadWait: "cannot read `claude -p /usage` ({tries}×): {detail} — asked again in {min} minutes",
|
|
87
107
|
noweek: "no line \"Current week … resets …\"",
|
|
108
|
+
said: "{detail}; it said: \"{line}\"",
|
|
109
|
+
nothing: "{detail}; it said nothing",
|
|
110
|
+
unused: "week 0 % used — nothing used yet",
|
|
88
111
|
exit: "it ended with exit status {status}",
|
|
112
|
+
timeout: "no answer in {s} s",
|
|
113
|
+
login: "Claude Code is not logged in — to log in once: claude, then /login",
|
|
114
|
+
loginDir: "Claude Code is not logged in with CLAUDE_CONFIG_DIR={dir} — to log in once with the same variable (CLAUDE_CONFIG_DIR={dir} claude, then /login), or to start strom without it",
|
|
89
115
|
},
|
|
90
116
|
cs: {
|
|
91
117
|
other: "agent není Claude Code — není co hlídat",
|
|
@@ -104,8 +130,15 @@ const T: Record<string, Record<string, string>> = {
|
|
|
104
130
|
badcap: "strop musí být 1 až 100 (\"--cap 95\"), ne \"{arg}\"",
|
|
105
131
|
cap: "týden: využito {used} % – strop {cap} % dosažen; obnoví se {resets}",
|
|
106
132
|
unread: "nelze přečíst `claude -p /usage`: {detail}",
|
|
133
|
+
unreadWait: "nelze přečíst `claude -p /usage` ({tries}×): {detail} — znovu za {min} minut",
|
|
107
134
|
noweek: "chybí řádek „Current week … resets …“",
|
|
135
|
+
said: "{detail}; vypsalo: „{line}“",
|
|
136
|
+
nothing: "{detail}; nevypsalo nic",
|
|
137
|
+
unused: "týden: využito 0 % — zatím nic",
|
|
108
138
|
exit: "skončilo s kódem {status}",
|
|
139
|
+
timeout: "bez odpovědi do {s} s",
|
|
140
|
+
login: "Claude Code není přihlášený — přihlásit jednou: claude, pak /login",
|
|
141
|
+
loginDir: "Claude Code není přihlášený s CLAUDE_CONFIG_DIR={dir} — přihlásit jednou se stejnou proměnnou (CLAUDE_CONFIG_DIR={dir} claude, pak /login), nebo spustit strom bez ní",
|
|
109
142
|
},
|
|
110
143
|
de: {
|
|
111
144
|
other: "der Agent ist nicht Claude Code — nichts zu prüfen",
|
|
@@ -124,8 +157,42 @@ const T: Record<string, Record<string, string>> = {
|
|
|
124
157
|
badcap: "die Obergrenze muss 1 bis 100 sein (\"--cap 95\"), nicht \"{arg}\"",
|
|
125
158
|
cap: "Woche: {used} % genutzt – Obergrenze {cap} % erreicht; zurückgesetzt {resets}",
|
|
126
159
|
unread: "`claude -p /usage` nicht lesbar: {detail}",
|
|
160
|
+
unreadWait: "`claude -p /usage` nicht lesbar ({tries}×): {detail} — erneut in {min} Minuten",
|
|
127
161
|
noweek: "keine Zeile „Current week … resets …“",
|
|
162
|
+
said: "{detail}; Ausgabe: „{line}“",
|
|
163
|
+
nothing: "{detail}; keine Ausgabe",
|
|
164
|
+
unused: "Woche: 0 % genutzt — noch nichts",
|
|
128
165
|
exit: "mit Exit-Status {status} beendet",
|
|
166
|
+
timeout: "keine Antwort in {s} s",
|
|
167
|
+
login: "Claude Code ist nicht angemeldet — einmal anmelden: claude, dann /login",
|
|
168
|
+
loginDir: "Claude Code ist mit CLAUDE_CONFIG_DIR={dir} nicht angemeldet — einmal mit derselben Variable anmelden (CLAUDE_CONFIG_DIR={dir} claude, dann /login) oder strom ohne sie starten",
|
|
169
|
+
},
|
|
170
|
+
pl: {
|
|
171
|
+
other: "agent to nie Claude Code — nie ma czego pilnować",
|
|
172
|
+
api: "Claude Code działa na kluczu API, nie w subskrypcji — bez limitów planu",
|
|
173
|
+
head: "tydzień: wykorzystano {used} %, upłynęło {gone} %, zapas {behind} (potrzeba co najmniej {min}{early})",
|
|
174
|
+
early: " – na początku tygodnia połowa tego, co upłynęło",
|
|
175
|
+
resetIn: "; tydzień odnowi się za {reset}",
|
|
176
|
+
hold: " — czeka na zapas {target}, potem kilka sesji z rzędu; tydzień odnowi się za {reset}",
|
|
177
|
+
last: " — do ostatnich {window} przed odnowieniem tygodnia za {reset}",
|
|
178
|
+
reset: " — do odnowienia tygodnia za {reset}",
|
|
179
|
+
end: "tydzień: wykorzystano {used} %, odnowi się za {reset} — pozostałe {room} % wystarczy na sesję",
|
|
180
|
+
session: "sesja wyczerpana — do jej odnowienia; tydzień odnowi się za {reset}",
|
|
181
|
+
hours: "{n} godz.",
|
|
182
|
+
days: "{n} dni",
|
|
183
|
+
bad: "liczba musi wynosić od 0 do 99, nie \"{arg}\"",
|
|
184
|
+
badcap: "limit musi wynosić od 1 do 100 (\"--cap 95\"), nie \"{arg}\"",
|
|
185
|
+
cap: "tydzień: wykorzystano {used} % – osiągnięto limit {cap} %; odnowi się {resets}",
|
|
186
|
+
unread: "nie można odczytać `claude -p /usage`: {detail}",
|
|
187
|
+
unreadWait: "nie można odczytać `claude -p /usage` ({tries}×): {detail} — ponownie za {min} minut",
|
|
188
|
+
noweek: "brak wiersza „Current week … resets …”",
|
|
189
|
+
said: "{detail}; wypisało: „{line}”",
|
|
190
|
+
nothing: "{detail}; nic nie wypisało",
|
|
191
|
+
unused: "tydzień: wykorzystano 0 % — jeszcze nic",
|
|
192
|
+
exit: "zakończyło się kodem {status}",
|
|
193
|
+
timeout: "brak odpowiedzi w ciągu {s} s",
|
|
194
|
+
login: "Claude Code nie jest zalogowany — zalogować się raz: claude, potem /login",
|
|
195
|
+
loginDir: "Claude Code nie jest zalogowany z CLAUDE_CONFIG_DIR={dir} — zalogować się raz z tą samą zmienną (CLAUDE_CONFIG_DIR={dir} claude, potem /login) albo uruchomić strom bez niej",
|
|
129
196
|
},
|
|
130
197
|
};
|
|
131
198
|
function say(key: string, v: Record<string, string | number> = {}): string {
|
|
@@ -167,26 +234,39 @@ let week: Use | undefined;
|
|
|
167
234
|
const said = fromAgent(process.env.STROM_AGENT_LIMITS);
|
|
168
235
|
if (said) ({ session, week } = said);
|
|
169
236
|
else {
|
|
170
|
-
// ── what Claude Code says
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
237
|
+
// ── what Claude Code says: asked again when it cannot be read, then a wait — never a stop for one answer ─────
|
|
238
|
+
const began = Date.now();
|
|
239
|
+
let detail = "";
|
|
240
|
+
let tries = 0;
|
|
241
|
+
for (;;) {
|
|
242
|
+
tries++;
|
|
243
|
+
const read = askUsage(Math.min(ASK_MS, BUDGET_MS - (Date.now() - began)));
|
|
244
|
+
if ("api" in read) {
|
|
245
|
+
cap = undefined;
|
|
246
|
+
answer(0, say("api"));
|
|
247
|
+
}
|
|
248
|
+
if ("week" in read) {
|
|
249
|
+
({ session, week } = read);
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
detail = read.detail;
|
|
253
|
+
// not logged in (its /usage a bare /cost): no asking again makes it read — said with how to log in
|
|
254
|
+
if (tries === 1 && loggedIn() === false) {
|
|
255
|
+
cap = undefined;
|
|
256
|
+
const dir = process.env.CLAUDE_CONFIG_DIR?.trim();
|
|
257
|
+
answer(2, dir ? say("loginDir", { dir }) : say("login"));
|
|
258
|
+
}
|
|
259
|
+
if (tries >= ATTEMPTS || BUDGET_MS - (Date.now() - began) < RETRY_MS + ASK_MS / 2) break;
|
|
260
|
+
sleep(RETRY_MS);
|
|
187
261
|
}
|
|
262
|
+
if (!week) answer(1, say("unreadWait", { tries, detail, min: UNREAD_WAIT / 60_000 }), new Date(Date.now() + UNREAD_WAIT));
|
|
263
|
+
}
|
|
264
|
+
if (!week) answer(2, say("unread", { detail: say("noweek") }));
|
|
265
|
+
// the week's line without its reset: nothing used yet goes (a new week), else what it says is not read
|
|
266
|
+
if (!week.resets) {
|
|
267
|
+
if (week.used === 0) answer(0, say("unused"));
|
|
268
|
+
answer(1, say("unreadWait", { tries: 1, detail: say("noweek"), min: UNREAD_WAIT / 60_000 }), new Date(Date.now() + UNREAD_WAIT));
|
|
188
269
|
}
|
|
189
|
-
if (!week?.resets) answer(2, say("unread", { detail: say("noweek") }));
|
|
190
270
|
// the cap first: the user's own hard stop, whatever the pace
|
|
191
271
|
if (cap !== undefined && week.used >= cap)
|
|
192
272
|
answer(2, say("cap", { used: Math.floor(week.used), cap, resets: week.resets.toLocaleString(lang, { weekday: "short", day: "numeric", month: "numeric", hour: "2-digit", minute: "2-digit" }) }), undefined, true);
|
|
@@ -231,6 +311,59 @@ const tail =
|
|
|
231
311
|
at === caught ? say("hold", { reset, target: Math.ceil(need + reserve) }) : at === last ? say("last", { reset, window: say("hours", { n: END_WINDOW / 3_600_000 }) }) : say("reset", { reset });
|
|
232
312
|
answer(1, say("head", { ...v, min: Math.ceil(need) }) + tail, new Date(Math.max(at, now + 5 * 60_000)));
|
|
233
313
|
|
|
314
|
+
/**
|
|
315
|
+
* `claude -p /usage` once: the session's and the week's use, an API key's answer, or why it was not read — with the
|
|
316
|
+
* first line it said (shortened, no e-mail address), so the gate's log says what Claude Code answered.
|
|
317
|
+
*/
|
|
318
|
+
function askUsage(timeout: number): { session?: Use; week: Use } | { api: true } | { detail: string } {
|
|
319
|
+
const r = spawnSync("claude", ["-p", "/usage"], { encoding: "utf8", timeout: Math.max(5_000, timeout), shell: process.platform === "win32", windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
320
|
+
// what Claude Code or the system said is said as it is; the gate's own words in the research language (STROM_LANG)
|
|
321
|
+
const out = `${r.stdout ?? ""}\n${r.stderr ?? ""}`;
|
|
322
|
+
const line = firstLine(r.status === 0 ? (r.stdout ?? "") : out);
|
|
323
|
+
const why = (detail: string) => ({ detail: line ? say("said", { detail, line }) : say("nothing", { detail }) });
|
|
324
|
+
if (r.error) return (r.error as NodeJS.ErrnoException).code === "ETIMEDOUT" ? why(say("timeout", { s: Math.round(timeout / 1000) })) : { detail: r.error.message };
|
|
325
|
+
if (r.status !== 0) return why(say("exit", { status: String(r.status) }));
|
|
326
|
+
const text = r.stdout ?? "";
|
|
327
|
+
if (/API key/i.test(text) && !/Current week/i.test(text)) return { api: true };
|
|
328
|
+
let session: Use | undefined;
|
|
329
|
+
let week: Use | undefined;
|
|
330
|
+
const LINE = /^Current (session|week)(?: \(([^)]+)\))?:\s*(\d+(?:\.\d+)?)\s*%\s*used(?:\s*·\s*resets\s*(.+))?$/i;
|
|
331
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
332
|
+
const m = LINE.exec(raw.trim());
|
|
333
|
+
if (!m) continue;
|
|
334
|
+
const at = m[4] ? resetAt(m[4]) : undefined;
|
|
335
|
+
const item = { used: Number(m[3]), ...(at ? { resets: at } : {}) };
|
|
336
|
+
if (m[1]!.toLowerCase() === "session") session = item;
|
|
337
|
+
// the limit of all models is the one that stops the work; a model's own week only if there is no other
|
|
338
|
+
else if (!week || /all models/i.test(m[2] ?? "")) week = item;
|
|
339
|
+
}
|
|
340
|
+
if (!week || (!week.resets && week.used !== 0)) return why(say("noweek"));
|
|
341
|
+
return { week, ...(session ? { session } : {}) };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/** The first line said that is not empty, shortened, without an e-mail address (an account's name stays out of the log). */
|
|
345
|
+
function firstLine(text: string): string {
|
|
346
|
+
const line = text.split(/\r?\n/).map((l) => l.trim()).find(Boolean) ?? "";
|
|
347
|
+
const clean = line.replace(/[\p{L}\p{N}._%+-]+@[\p{L}\p{N}.-]+/gu, "…@…");
|
|
348
|
+
return clean.length > 80 ? `${clean.slice(0, 79)}…` : clean;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** `claude auth status` says whether it is logged in; undefined when it cannot tell (an older Claude Code). */
|
|
352
|
+
function loggedIn(): boolean | undefined {
|
|
353
|
+
try {
|
|
354
|
+
const r = spawnSync("claude", ["auth", "status"], { encoding: "utf8", timeout: 20_000, shell: process.platform === "win32", windowsHide: true, stdio: ["ignore", "pipe", "ignore"] });
|
|
355
|
+
const said = JSON.parse((r.stdout ?? "").trim()) as { loggedIn?: unknown };
|
|
356
|
+
return typeof said?.loggedIn === "boolean" ? said.loggedIn : undefined;
|
|
357
|
+
} catch {
|
|
358
|
+
return undefined;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** Waits here (the gate is one short program: nothing else to do meanwhile). */
|
|
363
|
+
function sleep(ms: number): void {
|
|
364
|
+
if (ms > 0) Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
365
|
+
}
|
|
366
|
+
|
|
234
367
|
/** It waits for the reserve in this week (the week it remembers ends within a day of this one's reset). */
|
|
235
368
|
function holding(resets: number): boolean {
|
|
236
369
|
if (!HOLD) return false;
|
|
@@ -287,16 +420,33 @@ function fromAgent(json: string | undefined): { session?: Use; week: Use } | und
|
|
|
287
420
|
return { week, ...(session ? { session } : {}) };
|
|
288
421
|
}
|
|
289
422
|
|
|
290
|
-
/**
|
|
423
|
+
/**
|
|
424
|
+
* "Sep 30 at 2pm (Europe/Prague)", "Oct 2 at 11:30am (UTC)", "Nov 4 at 13:00 (Europe/Prague)", "Nov 4, 1pm (UTC)" → the
|
|
425
|
+
* moment; a time alone ("6pm (Europe/Prague)", a session's) → its next coming.
|
|
426
|
+
*/
|
|
291
427
|
function resetAt(s: string): Date | undefined {
|
|
292
|
-
const m = /^(
|
|
428
|
+
const m = /^(?:([A-Za-z]{3})[A-Za-z]*\.?\s+(\d{1,2})(?:\s+at|,)?\s+)?(?:at\s+)?(\d{1,2})(?::(\d{2}))?\s*([ap]\.?m\.?)?\s*\(([^)]+)\)/i.exec(s.trim());
|
|
293
429
|
if (!m) return undefined;
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
430
|
+
const ampm = m[5]?.replace(/\./g, "").toLowerCase();
|
|
431
|
+
const h = Number(m[3]);
|
|
432
|
+
if (ampm ? h < 1 || h > 12 : h > 23) return undefined;
|
|
433
|
+
const hour = ampm ? (h % 12) + (ampm === "pm" ? 12 : 0) : h;
|
|
434
|
+
const minute = Number(m[4] ?? 0);
|
|
297
435
|
const zone = m[6]!;
|
|
436
|
+
if (!m[1]) {
|
|
437
|
+
// a time alone: today's in the zone, else tomorrow's
|
|
438
|
+
const [y, mo, d] = new Intl.DateTimeFormat("en-CA", { timeZone: zone, year: "numeric", month: "2-digit", day: "2-digit" }).format(new Date()).split("-").map(Number) as [number, number, number];
|
|
439
|
+
for (const add of [0, 1]) {
|
|
440
|
+
const guess = Date.UTC(y, mo - 1, d + add, hour, minute);
|
|
441
|
+
const at = guess - offsetMs(guess, zone);
|
|
442
|
+
if (at > Date.now()) return new Date(at);
|
|
443
|
+
}
|
|
444
|
+
return undefined;
|
|
445
|
+
}
|
|
446
|
+
const month = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"].indexOf(m[1].toLowerCase());
|
|
447
|
+
if (month < 0) return undefined;
|
|
298
448
|
for (const year of [new Date().getUTCFullYear(), new Date().getUTCFullYear() + 1]) {
|
|
299
|
-
const guess = Date.UTC(year, month, Number(m[2]), hour,
|
|
449
|
+
const guess = Date.UTC(year, month, Number(m[2]), hour, minute);
|
|
300
450
|
const at = guess - offsetMs(guess, zone);
|
|
301
451
|
if (at > Date.now() - DAY) return new Date(at);
|
|
302
452
|
}
|