kimal3 0.4.0 → 0.5.0
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/README.md +4 -0
- package/coach/de/kimal3-coach.md +2 -2
- package/coach/en/kimal3-coach.md +2 -2
- package/dist/befehle/einrichten.js +1 -1
- package/dist/befehle/login.js +6 -2
- package/dist/befehle/skill-install.js +4 -1
- package/dist/befehle/zeigen-einrichten.js +104 -0
- package/dist/befehle/zeigen-start.js +143 -0
- package/dist/befehle/zeigen.js +288 -0
- package/dist/befehle-katalog-en.js +27 -0
- package/dist/befehle-katalog.js +27 -0
- package/dist/befehlswoerter.js +11 -0
- package/dist/kimal3.js +10 -0
- package/dist/logo-ablegen.js +26 -6
- package/dist/slash-befehle.js +1 -0
- package/dist/texte/einrichten.js +10 -3
- package/dist/texte/zeigen.js +189 -0
- package/dist/zeigen-rechnung.js +226 -0
- package/dist/zeigen-umgebung.js +190 -0
- package/package.json +2 -1
- package/skill/SKILL.md +3 -2
- package/skill/commands/k3.md +4 -3
- package/skill/commands/k3kurs.md +41 -41
- package/skill/commands/k3start.md +1 -1
- package/skill/commands/k3zeigen.md +303 -0
- package/skill/en/SKILL.md +3 -2
- package/skill/en/commands/k3.md +4 -3
- package/skill/en/commands/k3course.md +41 -41
- package/skill/en/commands/k3show.md +302 -0
- package/skill/en/commands/k3start.md +1 -1
- package/zeigen/gemeinsam.py +82 -0
- package/zeigen/starter.py +163 -0
- package/zeigen/werkzeug.py +250 -0
- package/zeigen/zeitleiste.py +393 -0
package/README.md
CHANGED
|
@@ -44,6 +44,8 @@ kimal3 projekt neu … Ein Projekt ins Erfolgsprofil eintragen
|
|
|
44
44
|
kimal3 projekt liste Deine eingetragenen Projekte anzeigen
|
|
45
45
|
kimal3 kurs Zeigen, wo du im Mini-Kurs stehst
|
|
46
46
|
kimal3 firma daten Die Daten deiner Firma holen, damit Claude sie auswerten kann
|
|
47
|
+
kimal3 zeigen start Eine Aufnahme starten, während du eine Aufgabe vormachst
|
|
48
|
+
kimal3 zeigen status Nachsehen, ob gerade aufgenommen wird und ob der Aufnehmer eingerichtet ist
|
|
47
49
|
kimal3 wache Läuft von selbst beim Start von Claude: prüft Zugang, Nachricht, Version, Skill
|
|
48
50
|
kimal3 wache aus Die tägliche Prüfung abschalten — sie sagt dann nichts mehr
|
|
49
51
|
kimal3 wache an Sie wieder einschalten
|
|
@@ -182,6 +184,8 @@ kimal3 project new … Add a project to your success profile
|
|
|
182
184
|
kimal3 project list Show the projects you've added
|
|
183
185
|
kimal3 course Show where you are in the mini-course
|
|
184
186
|
kimal3 company data Fetch your company's data so Claude can analyse it
|
|
187
|
+
kimal3 show start Start a recording while you show a task
|
|
188
|
+
kimal3 show status Check whether a recording is running and whether the recorder is set up
|
|
185
189
|
kimal3 watch Runs by itself when Claude starts: checks your access, messages, version and skill
|
|
186
190
|
kimal3 watch off Switch the daily check off — it then says nothing at all
|
|
187
191
|
kimal3 watch on Switch it back on
|
package/coach/de/kimal3-coach.md
CHANGED
|
@@ -118,8 +118,8 @@ nutze sie, statt vorschnell loszulegen.
|
|
|
118
118
|
Beispiel Node.js: `winget install OpenJS.NodeJS.LTS` / `brew install node`. Fehlt Homebrew, frag vorher: Es
|
|
119
119
|
einzurichten braucht einmal das Administrator-Passwort.
|
|
120
120
|
**Ohne Adminrechte** (oft am Firmenrechner) geht das nicht. Dann kommt das Werkzeug nur für dieses Konto in den
|
|
121
|
-
Benutzerordner, so wie
|
|
122
|
-
biete eine Nachricht an die IT an.
|
|
121
|
+
Benutzerordner, so wie die kimal3-Einrichtungsanleitung Node.js ohne Adminrechte nach `~/.kimal3/node` legt; sag dazu, dass es
|
|
122
|
+
dann nicht mit `upgrade` mitkommt, und biete eine Nachricht an die IT an.
|
|
123
123
|
**Ist ein Werkzeug schon da, aktualisiere es auf dem Weg, auf dem es gekommen ist** — sonst liegen zwei Fassungen
|
|
124
124
|
nebeneinander, und es ist unklar, welche startet.
|
|
125
125
|
- **Aktualisieren nach der Versionsnummer** (`16.2.10`): **letzte Zahl** = nur Fehler und Sicherheitslücken, zügig
|
package/coach/en/kimal3-coach.md
CHANGED
|
@@ -118,8 +118,8 @@ use them instead of starting off too quickly.
|
|
|
118
118
|
Example Node.js: `winget install OpenJS.NodeJS.LTS` / `brew install node`. If Homebrew is missing, ask first:
|
|
119
119
|
setting it up needs the administrator password once.
|
|
120
120
|
**Without admin rights** (often on a company computer) this does not work. Then the tool goes into the user folder,
|
|
121
|
-
for this account only, the way
|
|
122
|
-
and offer a message to IT.
|
|
121
|
+
for this account only, the way the kimal3 setup guide puts Node.js into `~/.kimal3/node` without admin rights; say that it
|
|
122
|
+
will then not come along with `upgrade`, and offer a message to IT.
|
|
123
123
|
**If a tool is already there, update it the way it came** — otherwise two versions sit side by side, and it is
|
|
124
124
|
unclear which one starts.
|
|
125
125
|
- **Update by version number** (`16.2.10`): **last number** = only bugs and security holes, take it
|
|
@@ -314,7 +314,7 @@ function pluginsEinrichten(git) {
|
|
|
314
314
|
// Ohne Logo-Ordner kommt superpowers trotzdem (Durchsicht 2, Fund 1): eine Zeile zum Logo, dann weiter.
|
|
315
315
|
if (!ablage.ok)
|
|
316
316
|
console.log(t.logoNotWritten(ablage.detail));
|
|
317
|
-
if (ablage.ok && ablage.
|
|
317
|
+
if (ablage.ok && ablage.stand === "alt")
|
|
318
318
|
console.log(t.logoOldVersion);
|
|
319
319
|
// Schrägstriche vorwärts: So steht der Pfad in Claudes Einstellungen auf jedem System gleich (P-13).
|
|
320
320
|
const eintrag = tragePluginsEin(ablage.ok ? ablage.ordner.replace(/\\/g, "/") : null, git);
|
package/dist/befehle/login.js
CHANGED
|
@@ -6,6 +6,7 @@ import { anmeldeAdresse, oeffneImBrowser } from "../browser.js";
|
|
|
6
6
|
import { ALLGEMEIN_TEXTE } from "../texte/allgemein.js";
|
|
7
7
|
import { EINRICHTEN_TEXTE } from "../texte/einrichten.js";
|
|
8
8
|
import { frischeCoachAuf } from "../coach-ablegen.js";
|
|
9
|
+
import { frischeLogoAuf } from "../logo-ablegen.js";
|
|
9
10
|
import { stelleSkillUm } from "./skill-tausch.js";
|
|
10
11
|
const warte = (ms) => new Promise((f) => setTimeout(f, ms));
|
|
11
12
|
// 🌐 Die Sprache wird bei JEDER Ausgabe neu gelesen: Die Abhol-Antwort nennt die Kontosprache
|
|
@@ -31,9 +32,12 @@ export async function login() {
|
|
|
31
32
|
const code = await warteAufBestaetigung(start.geraeteCode);
|
|
32
33
|
// Der Coach zieht mit der Kontosprache mit -- nur beim BEFEHL `kimal3 login`, nicht in der geteilten Warteschleife:
|
|
33
34
|
// `einrichten` meldet anschließend seinen eigenen Coach, eine zweite Zeile wäre doppelt (Gesamt-Durchsicht, Fund 4;
|
|
34
|
-
// dasselbe Muster wie `skillInstallBefehl`). Frischt nur auf, legt nie an (Spec C-13b).
|
|
35
|
-
|
|
35
|
+
// dasselbe Muster wie `skillInstallBefehl`). Frischt nur auf, legt nie an (Spec C-13b). Das Logo genauso (Spec
|
|
36
|
+
// 2026-10-18, L-1, L-8): `einrichten` legt es nach der Anmeldung ohnehin selbst ab.
|
|
37
|
+
if (code === 0) {
|
|
36
38
|
frischeCoachAuf(sprache());
|
|
39
|
+
frischeLogoAuf(sprache());
|
|
40
|
+
}
|
|
37
41
|
return code;
|
|
38
42
|
}
|
|
39
43
|
/**
|
|
@@ -10,6 +10,7 @@ import { sprache } from "../sprache.js";
|
|
|
10
10
|
import { andereSpracheZuRaeumen, skillQuelle } from "../slash-befehle.js";
|
|
11
11
|
import { SKILL_TEXTE } from "../texte/skill.js";
|
|
12
12
|
import { frischeCoachAuf } from "../coach-ablegen.js";
|
|
13
|
+
import { frischeLogoAuf } from "../logo-ablegen.js";
|
|
13
14
|
export const VERALTETE_BEFEHLE = [
|
|
14
15
|
/*
|
|
15
16
|
🔴 **Der ERSTE Eintrag ueberhaupt** (2026-08-28). `/k3firma` heisst jetzt
|
|
@@ -325,11 +326,13 @@ export async function skillInstall() {
|
|
|
325
326
|
* Der Befehl `kimal3 skill install`: die Anleitung ablegen und einen VORHANDENEN Coach mitziehen (Update, Sprachwechsel;
|
|
326
327
|
* angelegt wird keiner). Bewusst hier und nicht in `skillInstall()`: `einrichten` ruft das auch, hat seinen Coach aber
|
|
327
328
|
* schon selbst abgelegt und gemeldet -- ein zweites Auffrischen druckte dessen Zeile noch einmal (bei fehlender
|
|
328
|
-
* Trenn-Marke auch die Ersatzdatei ein zweites Mal).
|
|
329
|
+
* Trenn-Marke auch die Ersatzdatei ein zweites Mal). Das Logo zieht genauso mit, nur ein vorhandenes (Spec 2026-10-18,
|
|
330
|
+
* L-3 bis L-7): `einrichten` legt es selbst ab.
|
|
329
331
|
*/
|
|
330
332
|
export async function skillInstallBefehl() {
|
|
331
333
|
const code = await skillInstall();
|
|
332
334
|
frischeCoachAuf(sprache());
|
|
335
|
+
frischeLogoAuf(sprache());
|
|
333
336
|
return code;
|
|
334
337
|
}
|
|
335
338
|
/**
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { dirname, win32 } from "node:path";
|
|
3
|
+
import { zerlege } from "../argumente.js";
|
|
4
|
+
import { AUFNEHMER_FASSUNG, aufnehmerArgumente, einrichtPlan, teileAus, uvEinrichtung, } from "../zeigen-rechnung.js";
|
|
5
|
+
import { LESEN_MS, aufnehmerPython, findeUv, rufeWerkzeug, stillAusfuehren, uvBinOrdner } from "../zeigen-umgebung.js";
|
|
6
|
+
import { vorab } from "./zeigen.js";
|
|
7
|
+
/**
|
|
8
|
+
* `kimal3 zeigen einrichten` / `kimal3 show setup` (Spec 2026-10-18-k3zeigen-design.md, US-2, Szenarien S-1, S-3, S-15).
|
|
9
|
+
* Holt in dieser Reihenfolge nur, was fehlt: uv, den Aufnehmer 1.3.0, FFmpeg, das Sprachmodell. Ohne Adminrechte.
|
|
10
|
+
* 🔴 Kein fremdes Programm spricht hier mit dem Menschen: winget, uv und capture laufen mit `stdio: "ignore"`, vom
|
|
11
|
+
* Werkzeug zaehlt nur die eine JSON-Zeile. Ob ein Schritt geklappt hat, sagt nie sein Rueckgabewert allein, sondern
|
|
12
|
+
* die neue Bestandsaufnahme danach -- winget meldet z. B. auch "schon installiert" als Fehler.
|
|
13
|
+
*/
|
|
14
|
+
// Obergrenzen je Schritt: grosszuegig (langsame Leitung), aber nie "haengt fuer immer". Der Slash-Befehl startet
|
|
15
|
+
// `einrichten` im Hintergrund, weil Claude Code einen Werkzeugaufruf nach 10 Minuten abbricht (Spec US-2).
|
|
16
|
+
const MINUTE = 60_000;
|
|
17
|
+
const ZEIT_UV = 15 * MINUTE;
|
|
18
|
+
const ZEIT_AUFNEHMER = 45 * MINUTE;
|
|
19
|
+
const ZEIT_FFMPEG = 15 * MINUTE;
|
|
20
|
+
const ZEIT_MODELL = 60 * MINUTE;
|
|
21
|
+
/** Was jetzt da ist: uv (Suchpfad + bekannte Orte), und vom Aufnehmer selbst, was er hat. */
|
|
22
|
+
function bestand() {
|
|
23
|
+
const p = aufnehmerPython();
|
|
24
|
+
return { teile: teileAus(findeUv()?.uv ?? null, p === null ? null : rufeWerkzeug(p, ["teile"], null, LESEN_MS)), umgebungDa: p !== null };
|
|
25
|
+
}
|
|
26
|
+
function richteUvEin() {
|
|
27
|
+
const { befehl, argumente } = uvEinrichtung(stillAusfuehren("winget", ["--version"], LESEN_MS)); // kein-anzeigetext: Befehl
|
|
28
|
+
stillAusfuehren(befehl, argumente, ZEIT_UV);
|
|
29
|
+
// Der Suchpfad dieser Sitzung ist noch der alte: findeUv sieht auch an den bekannten Orten nach.
|
|
30
|
+
return findeUv() !== null;
|
|
31
|
+
}
|
|
32
|
+
function richteAufnehmerEin(umgebungDa) {
|
|
33
|
+
const uv = findeUv();
|
|
34
|
+
if (uv === null)
|
|
35
|
+
return false;
|
|
36
|
+
stillAusfuehren(uv.uv, aufnehmerArgumente(umgebungDa), ZEIT_AUFNEHMER);
|
|
37
|
+
return bestand().teile.aufnehmer === AUFNEHMER_FASSUNG;
|
|
38
|
+
}
|
|
39
|
+
function richteFfmpegEin() {
|
|
40
|
+
const uv = findeUv();
|
|
41
|
+
const p = aufnehmerPython();
|
|
42
|
+
if (uv === null || p === null)
|
|
43
|
+
return false;
|
|
44
|
+
const bin = uvBinOrdner(uv.uv);
|
|
45
|
+
// `capture.exe` liegt im Programmordner von uv und neben dem Python des Aufnehmers; der erste, den es gibt.
|
|
46
|
+
const capture = [bin === null ? null : win32.join(bin, "capture.exe"), win32.join(dirname(p.python), "capture.exe")] // kein-anzeigetext: Dateiname
|
|
47
|
+
.find((d) => d !== null && existsSync(d));
|
|
48
|
+
if (capture === undefined)
|
|
49
|
+
return false;
|
|
50
|
+
stillAusfuehren(capture, ["install-ffmpeg"], ZEIT_FFMPEG); // kein-anzeigetext: Befehl
|
|
51
|
+
return bestand().teile.ffmpeg;
|
|
52
|
+
}
|
|
53
|
+
function richteModellEin() {
|
|
54
|
+
const p = aufnehmerPython();
|
|
55
|
+
if (p === null)
|
|
56
|
+
return false;
|
|
57
|
+
const r = rufeWerkzeug(p, ["modell-laden"], null, ZEIT_MODELL); // kein-anzeigetext: Befehl
|
|
58
|
+
return r.ok && (r.daten.ergebnis === "da" || r.daten.ergebnis === "geladen");
|
|
59
|
+
}
|
|
60
|
+
function zeile(t, schritt) {
|
|
61
|
+
if (schritt === "uv")
|
|
62
|
+
return t.schrittUv;
|
|
63
|
+
if (schritt === "aufnehmer")
|
|
64
|
+
return t.schrittAufnehmer(AUFNEHMER_FASSUNG);
|
|
65
|
+
return schritt === "ffmpeg" ? t.schrittFfmpeg : t.schrittModell;
|
|
66
|
+
}
|
|
67
|
+
export async function zeigenEinrichten(rest) {
|
|
68
|
+
const lage = vorab(rest, (t) => t.helpEinrichten);
|
|
69
|
+
if (typeof lage === "number")
|
|
70
|
+
return lage;
|
|
71
|
+
const { s, t } = lage;
|
|
72
|
+
const z = zerlege(rest, [], s);
|
|
73
|
+
if (!z.ok) {
|
|
74
|
+
console.error(`${z.meldung}\n${t.helpEinrichten}`);
|
|
75
|
+
return 1;
|
|
76
|
+
}
|
|
77
|
+
const anfang = bestand();
|
|
78
|
+
const plan = einrichtPlan(anfang.teile);
|
|
79
|
+
if (plan.length === 0) {
|
|
80
|
+
console.log(t.allesDa(AUFNEHMER_FASSUNG));
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
for (const schritt of plan) {
|
|
84
|
+
// FFmpeg und Modell erst nach einem neuen Blick: Ein frisch eingerichteter Aufnehmer findet z. B. ein FFmpeg im Suchpfad.
|
|
85
|
+
if ((schritt === "ffmpeg" || schritt === "modell") && bestand().teile[schritt])
|
|
86
|
+
continue;
|
|
87
|
+
console.log(zeile(t, schritt));
|
|
88
|
+
const geklappt = schritt === "uv" ? richteUvEin()
|
|
89
|
+
: schritt === "aufnehmer" ? richteAufnehmerEin(anfang.umgebungDa)
|
|
90
|
+
: schritt === "ffmpeg" ? richteFfmpegEin() : richteModellEin();
|
|
91
|
+
if (!geklappt) {
|
|
92
|
+
console.error(t.nichtGeklappt(schritt));
|
|
93
|
+
return 1;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Die Schlussmeldung nur, wenn die Bestandsaufnahme danach wirklich alle vier zeigt -- nie aus dem Plan geschlossen.
|
|
97
|
+
const offen = einrichtPlan(bestand().teile);
|
|
98
|
+
if (offen.length > 0) {
|
|
99
|
+
console.error(t.nichtGeklappt(offen[0]));
|
|
100
|
+
return 1;
|
|
101
|
+
}
|
|
102
|
+
console.log(t.fertigEingerichtet(AUFNEHMER_FASSUNG));
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { trenneNamenUndOptionen, zerlege } from "../argumente.js";
|
|
4
|
+
import { einrichtPlan, fehlerGrund } from "../zeigen-rechnung.js";
|
|
5
|
+
import { LESEN_MS, aufnehmerPython, leseStarter, rufeWerkzeug, schreibeNotiz, starteStarter, starterFakt, uvFassung } from "../zeigen-umgebung.js";
|
|
6
|
+
import { ermittleZustand, grundSatz, gueltigeFassung, ortHier, uhrzeit, vorab, zeigeOrdner } from "./zeigen.js";
|
|
7
|
+
/**
|
|
8
|
+
* `kimal3 zeigen start "<Satz>" [--hoechstens N]` (Spec US-3, US-4, US-5). Reihenfolge: Ort -> laeuft schon etwas? ->
|
|
9
|
+
* eingerichtet? -> Ordner -> Starter abgehaengt -> warten, bis er "laeuft, Sitzung …" meldet.
|
|
10
|
+
* 🔴 Der Satz geht nur nach `aufgabe.txt`, nie an den Aufnehmer (dessen Befehlszeile zerlegt ihn, Spec US-4).
|
|
11
|
+
* 🔴 Scheitert der Start, zieht `start` die Bremse (`abbrechen`) und wartet, bis der Starter das Ende bestaetigt -- ein
|
|
12
|
+
* Fehlschlag darf keinen Aufnehmer zuruecklassen. Gemeldet wird nur ein fester Grund und der Pfad zum Protokoll.
|
|
13
|
+
*/
|
|
14
|
+
const HOECHSTENS_VORGABE = 15;
|
|
15
|
+
const TAKT_MS = 500;
|
|
16
|
+
const WARTEN_START_MS = 60_000; // der Aufnehmer selbst wartet bis 60 s auf seine Bereitschaft
|
|
17
|
+
const WARTEN_ENDE_MS = 25_000; // R-14, Punkt 4: Wegraeumen samt Nachtoeten braucht laenger als 15 s
|
|
18
|
+
const warte = (ms) => new Promise((fertig) => setTimeout(fertig, ms));
|
|
19
|
+
const laeuftNoch = (zustand) => zustand === "startet" || zustand === "laeuft";
|
|
20
|
+
/** Bis `fertig` den Bericht des Starters annimmt (oder die Zeit um ist); der letzte gelesene Bericht. */
|
|
21
|
+
async function warteAufStarter(ordner, ms, fertig) {
|
|
22
|
+
const bis = Date.now() + ms;
|
|
23
|
+
let fakt = starterFakt(leseStarter(ordner));
|
|
24
|
+
while (!fertig(fakt) && Date.now() < bis) {
|
|
25
|
+
await warte(TAKT_MS);
|
|
26
|
+
fakt = starterFakt(leseStarter(ordner));
|
|
27
|
+
}
|
|
28
|
+
return fakt;
|
|
29
|
+
}
|
|
30
|
+
function legeOrdnerAn(t, aufnahmen, ordner, anzeige) {
|
|
31
|
+
try {
|
|
32
|
+
mkdirSync(aufnahmen, { recursive: true });
|
|
33
|
+
// Git UND Claudes Dateisuche fernhalten (Spec US-3); nur anlegen, was fehlt.
|
|
34
|
+
for (const name of [".gitignore", ".ignore"]) {
|
|
35
|
+
const datei = join(aufnahmen, name);
|
|
36
|
+
if (!existsSync(datei))
|
|
37
|
+
writeFileSync(datei, "*\n", "utf8");
|
|
38
|
+
}
|
|
39
|
+
mkdirSync(ordner); // ohne `recursive`: Gibt es ihn schon, war ein zweiter Start schneller (Review Focus 4)
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
return e.code === "EEXIST" ? t.startetGerade(anzeige) : t.ordnerNichtAnlegbar(ordner);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export async function zeigenStart(rest) {
|
|
47
|
+
const lage = vorab(rest, (t) => t.helpStart);
|
|
48
|
+
if (typeof lage === "number")
|
|
49
|
+
return lage;
|
|
50
|
+
const { s, t } = lage;
|
|
51
|
+
const { woerter, optionen } = trenneNamenUndOptionen(rest);
|
|
52
|
+
const satz = woerter.join(" ").trim();
|
|
53
|
+
const z = zerlege(optionen, ["hoechstens"], s);
|
|
54
|
+
if (!z.ok || satz === "") {
|
|
55
|
+
console.error(`${z.ok ? t.satzFehlt : z.meldung}\n${t.helpStart}`);
|
|
56
|
+
return 1;
|
|
57
|
+
}
|
|
58
|
+
const roh = z.werte.optionen.hoechstens ?? String(HOECHSTENS_VORGABE);
|
|
59
|
+
const minuten = /^\d+$/.test(roh) ? Number(roh) : NaN;
|
|
60
|
+
if (!(minuten >= 1 && minuten <= HOECHSTENS_VORGABE)) {
|
|
61
|
+
console.error(t.hoechstensFalsch(roh));
|
|
62
|
+
return 1;
|
|
63
|
+
}
|
|
64
|
+
const jetzt = new Date();
|
|
65
|
+
const ort = ortHier(jetzt);
|
|
66
|
+
if (ort.art !== "ort") {
|
|
67
|
+
console.error(ort.art === "werkbank" ? t.ortWerkbank : t.ortZuLang(ort.laenge, ort.hoechstens));
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
const p = aufnehmerPython();
|
|
71
|
+
if (p !== null) {
|
|
72
|
+
const { zustand } = ermittleZustand(p, ort, jetzt.getTime() / 1000);
|
|
73
|
+
if (zustand.art === "unbekannt") {
|
|
74
|
+
console.error(t.unbekanntKeinStart);
|
|
75
|
+
return 1;
|
|
76
|
+
}
|
|
77
|
+
if (zustand.art === "laeuft") {
|
|
78
|
+
const bekannt = !zustand.fremd && zustand.gestartet !== null && zustand.ordner !== null;
|
|
79
|
+
console.error(bekannt ? t.schonLaeuft(uhrzeit(zustand.gestartet), zeigeOrdner(zustand.ordner, ort)) : t.schonLaeuftFremd);
|
|
80
|
+
return 1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const teile = p === null ? null : rufeWerkzeug(p, ["teile"], null, LESEN_MS);
|
|
84
|
+
const fehlt = !teile?.ok || einrichtPlan({
|
|
85
|
+
uv: uvFassung(),
|
|
86
|
+
aufnehmer: gueltigeFassung(teile.daten.aufnehmer),
|
|
87
|
+
ffmpeg: teile.daten.ffmpeg === true,
|
|
88
|
+
modell: teile.daten.modell === true,
|
|
89
|
+
}).length > 0;
|
|
90
|
+
if (p === null || fehlt) {
|
|
91
|
+
console.error(t.nichtEingerichtet);
|
|
92
|
+
return 1;
|
|
93
|
+
}
|
|
94
|
+
const ordner = ort.ordner;
|
|
95
|
+
const anzeige = zeigeOrdner(ordner, ort);
|
|
96
|
+
const absage = legeOrdnerAn(t, ort.aufnahmen, ordner, anzeige);
|
|
97
|
+
if (absage !== null) {
|
|
98
|
+
console.error(absage);
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
101
|
+
const konfig = join(ordner, "konfig.json"); // kein-anzeigetext: Dateiname
|
|
102
|
+
try {
|
|
103
|
+
writeFileSync(join(ordner, "aufgabe.txt"), satz, "utf8"); // kein-anzeigetext: Dateiname
|
|
104
|
+
writeFileSync(konfig, JSON.stringify({ ordner, hoechstens_sek: minuten * 60 }), "utf8");
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
console.error(t.ordnerNichtAnlegbar(ordner));
|
|
108
|
+
return 1;
|
|
109
|
+
}
|
|
110
|
+
const protokoll = join(ordner, "aufnahme.log"); // kein-anzeigetext: Dateiname
|
|
111
|
+
let bericht = starteStarter(p, konfig, ordner)
|
|
112
|
+
? await warteAufStarter(ordner, WARTEN_START_MS, (f) => (f?.zustand === "laeuft" && f.sitzung !== undefined) || (f !== null && !laeuftNoch(f.zustand)))
|
|
113
|
+
: null;
|
|
114
|
+
const lauf = leseStarter(ordner);
|
|
115
|
+
if (bericht?.zustand === "laeuft" && bericht.sitzung !== undefined && typeof lauf?.gestartet === "number" && typeof lauf.frist === "number") {
|
|
116
|
+
schreibeNotiz({ sitzung: bericht.sitzung, ordner, gestartet: lauf.gestartet, frist: lauf.frist });
|
|
117
|
+
console.log(t.laeuftJetzt(anzeige, uhrzeit(lauf.frist)));
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
// Nicht gestartet: Bremse ziehen und auf die Bestaetigung warten, dass nichts weiterlaeuft.
|
|
121
|
+
try {
|
|
122
|
+
writeFileSync(join(ordner, "abbrechen"), "", "utf8"); // kein-anzeigetext: Dateiname
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
/* ohne Bremse meldet unten "nicht bestaetigt" */
|
|
126
|
+
}
|
|
127
|
+
bericht = await warteAufStarter(ordner, WARTEN_ENDE_MS, (f) => f !== null && !laeuftNoch(f.zustand));
|
|
128
|
+
const grund = bericht?.grund ?? fehlerGrund(liesProtokoll(protokoll));
|
|
129
|
+
console.error(t.nichtGestartet(grundSatz(t, grund), protokoll));
|
|
130
|
+
// Ohne Bericht (Starter nie angelaufen) ist das Ende genauso wenig bestaetigt wie bei "laeuft noch".
|
|
131
|
+
if (bericht === null || laeuftNoch(bericht.zustand) || bericht.grund === "nicht-beendet")
|
|
132
|
+
console.error(t.endeUnbestaetigt);
|
|
133
|
+
return 1;
|
|
134
|
+
}
|
|
135
|
+
/** Nur fuer `fehlerGrund` -- das Protokoll traegt Fenstertitel und wird nie gezeigt. */
|
|
136
|
+
function liesProtokoll(pfad) {
|
|
137
|
+
try {
|
|
138
|
+
return readFileSync(pfad, "utf8");
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return "";
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { platform } from "node:os";
|
|
3
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
|
+
import { schalterOhneWert, trenneNamenUndOptionen, zerlege } from "../argumente.js";
|
|
5
|
+
import { sprache } from "../sprache.js";
|
|
6
|
+
import { ZEIGEN_TEXTE } from "../texte/zeigen.js";
|
|
7
|
+
import { AUFNEHMER_FASSUNG, aufnahmeOrt, aufnahmeZustand, bildName, dauerText, gueltigeFassung, starterLebt, stoppAusgang, zeitAusText, zustandOhneAufnehmer, } from "../zeigen-rechnung.js";
|
|
8
|
+
import { LESEN_MS, STOPPEN_MS, aufnehmerPython, juengsterOrdner, leseNotiz, leseStarter, loescheNotiz, rufeWerkzeug, starterFakt, uvFassung, } from "../zeigen-umgebung.js";
|
|
9
|
+
/** `--help` (auch auf dem Mac), dann die Plattform. Eine Zahl = fertig, sonst die Lage fuer den Befehl. */
|
|
10
|
+
export function vorab(rest, hilfe) {
|
|
11
|
+
const s = sprache();
|
|
12
|
+
const t = ZEIGEN_TEXTE[s];
|
|
13
|
+
if (rest.includes("--help") || rest.includes("-h")) {
|
|
14
|
+
console.log(hilfe(t));
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
if (platform() !== "win32") {
|
|
18
|
+
console.error(t.nurWindows);
|
|
19
|
+
return 1;
|
|
20
|
+
}
|
|
21
|
+
return { s, t };
|
|
22
|
+
}
|
|
23
|
+
export const sekundenJetzt = () => Date.now() / 1000;
|
|
24
|
+
export const ortHier = (jetzt) => aufnahmeOrt(process.cwd(), jetzt, platform(), existsSync);
|
|
25
|
+
/** Ein Aufnahmeordner zum Zeigen: im Projekt relativ mit `/` (`material/aufnahmen/…`), sonst der volle Pfad. */
|
|
26
|
+
export function zeigeOrdner(ordner, ort) {
|
|
27
|
+
if (ort.art !== "ort")
|
|
28
|
+
return ordner;
|
|
29
|
+
const rel = relative(dirname(dirname(ort.aufnahmen)), ordner);
|
|
30
|
+
return rel === "" || rel.startsWith("..") || isAbsolute(rel) ? ordner : rel.split("\\").join("/");
|
|
31
|
+
}
|
|
32
|
+
/** Epochensekunden -> "09:12" in Ortszeit. */
|
|
33
|
+
export function uhrzeit(sek) {
|
|
34
|
+
const d = new Date(sek * 1000);
|
|
35
|
+
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
36
|
+
}
|
|
37
|
+
/** Ein Code vom Werkzeug -> fester Satz. Was nicht in `gruende` steht, wird nie gezeigt, sondern `unbekannt` (R-14, Punkt 9). */
|
|
38
|
+
export function grundSatz(t, code) {
|
|
39
|
+
return typeof code === "string" && Object.hasOwn(t.gruende, code) ? t.gruende[code] : t.gruende.unbekannt;
|
|
40
|
+
}
|
|
41
|
+
/** Wohnt seit Task 5 in `zeigen-rechnung.ts` (auch `einrichten` braucht sie); hier weitergereicht fuer `start`. */
|
|
42
|
+
export { gueltigeFassung };
|
|
43
|
+
const istZahl = (w) => typeof w === "number" && Number.isFinite(w);
|
|
44
|
+
const istZaehlung = (w) => istZahl(w) && Number.isInteger(w) && w >= 0;
|
|
45
|
+
/**
|
|
46
|
+
* Welcher Ordner gehoert zur Frage? Die Notiz zuerst, sonst die juengste Aufnahme im Projekt. Eine Notiz, deren Ordner
|
|
47
|
+
* es nicht mehr gibt, zaehlt nicht (`veraltet`): Ein Aufnehmer, der noch schreibt, haelt seinen Ordner fest.
|
|
48
|
+
*/
|
|
49
|
+
function ordnerZurFrage(ort) {
|
|
50
|
+
const gelesen = leseNotiz();
|
|
51
|
+
const notiz = gelesen !== null && existsSync(gelesen.ordner) ? gelesen : null;
|
|
52
|
+
const kandidat = notiz?.ordner ?? (ort.art === "ort" ? juengsterOrdner(ort.aufnahmen) : null);
|
|
53
|
+
return { ordner: kandidat !== null && existsSync(kandidat) ? kandidat : null, notiz, veraltet: gelesen !== null && notiz === null };
|
|
54
|
+
}
|
|
55
|
+
/** Die aktiven Sitzungen aus einer `zustand`-Antwort; `null` = weiss nicht. */
|
|
56
|
+
function aktiveAus(r) {
|
|
57
|
+
const liste = r.ok ? r.daten.aktive_sitzungen : null;
|
|
58
|
+
return Array.isArray(liste) && liste.every((x) => typeof x === "string") ? liste : null;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Laeuft eine Aufnahme? Mit dem Python des Aufnehmers fragt `zustand` ihn selbst; ohne zaehlen nur Notiz und Starter
|
|
62
|
+
* (Ruling R-13, `zustandOhneAufnehmer`).
|
|
63
|
+
*/
|
|
64
|
+
export function ermittleZustand(p, ort, jetzt) {
|
|
65
|
+
const { ordner, notiz } = ordnerZurFrage(ort);
|
|
66
|
+
const starterImNotizOrdner = notiz !== null && ordner === notiz.ordner;
|
|
67
|
+
if (p === null) {
|
|
68
|
+
const starter = ordner === null ? null : starterFakt(leseStarter(ordner));
|
|
69
|
+
return { zustand: zustandOhneAufnehmer({ notiz, starter, starterImNotizOrdner }, jetzt), ordner };
|
|
70
|
+
}
|
|
71
|
+
const r = rufeWerkzeug(p, ["zustand", ordner ?? "-"], ordner, LESEN_MS);
|
|
72
|
+
const starter = r.ok ? starterFakt(r.daten.starter) : null;
|
|
73
|
+
return { zustand: aufnahmeZustand({ aktiveSitzungen: aktiveAus(r), notiz, starter, starterImNotizOrdner }, jetzt), ordner };
|
|
74
|
+
}
|
|
75
|
+
// ── status ────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
76
|
+
export async function zeigenStatus(rest) {
|
|
77
|
+
const lage = vorab(rest, (t) => t.helpStatus);
|
|
78
|
+
if (typeof lage === "number")
|
|
79
|
+
return lage;
|
|
80
|
+
const { s, t } = lage;
|
|
81
|
+
const z = zerlege(rest, [], s);
|
|
82
|
+
if (!z.ok) {
|
|
83
|
+
console.error(`${z.meldung}\n${t.helpStatus}`);
|
|
84
|
+
return 1;
|
|
85
|
+
}
|
|
86
|
+
const jetzt = new Date();
|
|
87
|
+
const ort = ortHier(jetzt);
|
|
88
|
+
const p = aufnehmerPython();
|
|
89
|
+
const uv = uvFassung();
|
|
90
|
+
const zeilen = [t.eingerichtet, uv === null ? t.uvFehlt : t.uvDa(uv)];
|
|
91
|
+
const teile = p === null ? null : rufeWerkzeug(p, ["teile"], null, LESEN_MS);
|
|
92
|
+
if (p === null)
|
|
93
|
+
zeilen.push(t.aufnehmerFehlt, t.teilUngeprueft("ffmpeg"), t.teilUngeprueft("modell"));
|
|
94
|
+
else if (!teile?.ok)
|
|
95
|
+
zeilen.push(t.teilUnbekannt("aufnehmer"), t.teilUnbekannt("ffmpeg"), t.teilUnbekannt("modell"));
|
|
96
|
+
else {
|
|
97
|
+
const roh = teile.daten.aufnehmer;
|
|
98
|
+
const f = gueltigeFassung(roh);
|
|
99
|
+
zeilen.push(roh === null ? t.aufnehmerFehlt
|
|
100
|
+
: f === null ? t.teilUnbekannt("aufnehmer")
|
|
101
|
+
: f === AUFNEHMER_FASSUNG ? t.aufnehmerDa(f) : t.aufnehmerAlt(f, AUFNEHMER_FASSUNG));
|
|
102
|
+
zeilen.push(t.ffmpeg(teile.daten.ffmpeg === true), t.modell(teile.daten.modell === true));
|
|
103
|
+
}
|
|
104
|
+
const { zustand, ordner } = ermittleZustand(p, ort, jetzt.getTime() / 1000);
|
|
105
|
+
let code = 0;
|
|
106
|
+
zeilen.push(t.aufnahme);
|
|
107
|
+
if (zustand.art === "unbekannt") {
|
|
108
|
+
zeilen.push(` ${t.unbekannt}`);
|
|
109
|
+
code = 1;
|
|
110
|
+
}
|
|
111
|
+
else if (zustand.art === "laeuft") {
|
|
112
|
+
const { gestartet, frist } = zustand;
|
|
113
|
+
if (zustand.fremd || gestartet === null || frist === null || zustand.ordner === null)
|
|
114
|
+
zeilen.push(t.laeuftFremd);
|
|
115
|
+
else {
|
|
116
|
+
const minuten = Math.max(0, Math.floor(jetzt.getTime() / 60000 - gestartet / 60));
|
|
117
|
+
zeilen.push(t.laeuftSeit(uhrzeit(gestartet), minuten, zeigeOrdner(zustand.ordner, ort), uhrzeit(frist)));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
zeilen.push(t.keineLaeuft);
|
|
122
|
+
const roh = ordner === null ? null : leseStarter(ordner);
|
|
123
|
+
if (ordner !== null && zustand.letzte === "selbst-gestoppt") {
|
|
124
|
+
const min = istZahl(roh?.frist) && istZahl(roh?.gestartet) ? Math.round((roh.frist - roh.gestartet) / 60) : 15;
|
|
125
|
+
zeilen.push(t.letzteSelbst(zeigeOrdner(ordner, ort), min));
|
|
126
|
+
}
|
|
127
|
+
if (ordner !== null && zustand.letzte === "abgebrochen")
|
|
128
|
+
zeilen.push(t.letzteAbgebrochen(zeigeOrdner(ordner, ort)));
|
|
129
|
+
}
|
|
130
|
+
if (ort.art === "ort")
|
|
131
|
+
zeilen.push(t.ortHier(ort.aufnahmen));
|
|
132
|
+
else
|
|
133
|
+
zeilen.push(ort.art === "werkbank" ? t.ortWerkbank : t.ortZuLang(ort.laenge, ort.hoechstens));
|
|
134
|
+
console.log(zeilen.join("\n"));
|
|
135
|
+
return code;
|
|
136
|
+
}
|
|
137
|
+
// ── stopp ─────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
138
|
+
export async function zeigenStopp(rest) {
|
|
139
|
+
const lage = vorab(rest, (t) => t.helpStopp);
|
|
140
|
+
if (typeof lage === "number")
|
|
141
|
+
return lage;
|
|
142
|
+
const { s, t } = lage;
|
|
143
|
+
const z = zerlege(rest, [], s);
|
|
144
|
+
if (!z.ok) {
|
|
145
|
+
console.error(`${z.meldung}\n${t.helpStopp}`);
|
|
146
|
+
return 1;
|
|
147
|
+
}
|
|
148
|
+
const ort = ortHier(new Date());
|
|
149
|
+
const { ordner, notiz, veraltet } = ordnerZurFrage(ort);
|
|
150
|
+
if (veraltet)
|
|
151
|
+
loescheNotiz(); // ihr Ordner ist weg: die Notiz zeigt ins Leere
|
|
152
|
+
const p = aufnehmerPython();
|
|
153
|
+
// Eigene Aufnahme = eine Notiz, oder ein Starter, der noch "startet"/"laeuft" sagt. Eine alte, laengst gestoppte
|
|
154
|
+
// Aufnahme ohne Notiz ist keine laufende.
|
|
155
|
+
const starter = ordner === null ? null : starterFakt(leseStarter(ordner));
|
|
156
|
+
const eigene = ordner !== null && (notiz !== null || starter?.zustand === "startet" || starter?.zustand === "laeuft");
|
|
157
|
+
if (!eigene) {
|
|
158
|
+
// R-13: ohne Aufnehmer kann nichts laufen, was kimal3 gestartet hat. Mit ihm fragen, ob eine FREMDE laeuft (Review, Punkt 2).
|
|
159
|
+
const aktive = p === null ? [] : aktiveAus(rufeWerkzeug(p, ["zustand", "-"], null, LESEN_MS));
|
|
160
|
+
if (aktive === null)
|
|
161
|
+
console.error(t.unbekannt);
|
|
162
|
+
else if (aktive.length > 0)
|
|
163
|
+
console.error(t.fremdLaeuft);
|
|
164
|
+
else
|
|
165
|
+
console.log(t.keineAufnahmeLaeuft);
|
|
166
|
+
return aktive?.length === 0 ? 0 : 1;
|
|
167
|
+
}
|
|
168
|
+
if (p === null) {
|
|
169
|
+
// Eine eigene Aufnahme ist bekannt, der Aufnehmer aber nicht zu finden: stoppen kann ich nicht, "keine" waere gelogen.
|
|
170
|
+
console.error(t.stoppUnklar);
|
|
171
|
+
return 1;
|
|
172
|
+
}
|
|
173
|
+
const r = rufeWerkzeug(p, ["stoppen", ordner], ordner, STOPPEN_MS);
|
|
174
|
+
const d = r.ok ? r.daten : {};
|
|
175
|
+
const zahlen = istZahl(d.dauer_sek) && istZaehlung(d.klicks) && istZaehlung(d.tasten) && typeof d.ton === "boolean"
|
|
176
|
+
? t.zahlen(dauerText(d.dauer_sek), d.klicks, d.tasten, d.ton) : null;
|
|
177
|
+
const ausgang = stoppAusgang(d.ergebnis, zahlen !== null);
|
|
178
|
+
// Werkzeug stumm, unlesbar oder noch beim Abschliessen: nichts ist bestaetigt -- die Notiz bleibt, damit status und
|
|
179
|
+
// der naechste stopp die Aufnahme weiter finden (Endpruefung, Punkt 2).
|
|
180
|
+
if (ausgang.notizWeg && notiz?.ordner === ordner)
|
|
181
|
+
loescheNotiz();
|
|
182
|
+
const roh = leseStarter(ordner);
|
|
183
|
+
const min = istZahl(roh?.frist) && istZahl(roh?.gestartet) ? Math.round((roh.frist - roh.gestartet) / 60) : 15;
|
|
184
|
+
const satz = {
|
|
185
|
+
keine: t.keineAufnahmeLaeuft,
|
|
186
|
+
gestoppt: t.gestoppt(zahlen ?? ""),
|
|
187
|
+
"selbst-gestoppt": t.selbstGestoppt(min, zahlen ?? ""),
|
|
188
|
+
"nicht-sauber": t.nichtSauber(grundSatz(t, d.grund)),
|
|
189
|
+
abgebrochen: t.abgebrochen(grundSatz(t, d.grund), zahlen),
|
|
190
|
+
"wird-abgeschlossen": t.wirdAbgeschlossen,
|
|
191
|
+
unklar: t.stoppUnklar,
|
|
192
|
+
}[ausgang.satz];
|
|
193
|
+
(ausgang.code === 0 ? console.log : console.error)(satz);
|
|
194
|
+
return ausgang.code;
|
|
195
|
+
}
|
|
196
|
+
// ── auswerten und bild: der Aufnahmeordner ───────────────────────────────────────────────────────────────────────
|
|
197
|
+
/** Der genannte oder der juengste Aufnahmeordner, der nicht mehr laeuft -- sonst ein Satz auf stderr und `null`. */
|
|
198
|
+
function fertigerOrdner(t, genannt, ort) {
|
|
199
|
+
const ordner = genannt !== undefined ? resolve(genannt) : ort.art === "ort" ? juengsterOrdner(ort.aufnahmen) : null;
|
|
200
|
+
if (ordner === null)
|
|
201
|
+
console.error(t.keineAufnahmeHier);
|
|
202
|
+
else if (!existsSync(ordner))
|
|
203
|
+
console.error(t.ordnerFehlt(ordner));
|
|
204
|
+
// Nur ein LEBENDER Starter blockiert (R-3): ein toter mit stehengebliebenem "laeuft" gilt als abgebrochen (Review, Punkt 3).
|
|
205
|
+
else if (starterLebt(starterFakt(leseStarter(ordner)), sekundenJetzt()))
|
|
206
|
+
console.error(t.laeuftNoch);
|
|
207
|
+
else
|
|
208
|
+
return ordner;
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
export async function zeigenAuswerten(rest) {
|
|
212
|
+
const lage = vorab(rest, (t) => t.helpAuswerten);
|
|
213
|
+
if (typeof lage === "number")
|
|
214
|
+
return lage;
|
|
215
|
+
const { s, t } = lage;
|
|
216
|
+
const ohne = schalterOhneWert(rest, "ohne-getipptes");
|
|
217
|
+
const { woerter, optionen } = trenneNamenUndOptionen(ohne.rest);
|
|
218
|
+
const z = zerlege([...woerter.slice(1), ...optionen], ["sprache"], s);
|
|
219
|
+
if (!z.ok) {
|
|
220
|
+
console.error(`${z.meldung}\n${t.helpAuswerten}`);
|
|
221
|
+
return 1;
|
|
222
|
+
}
|
|
223
|
+
const erkennung = z.werte.optionen.sprache ?? s;
|
|
224
|
+
if (erkennung !== "de" && erkennung !== "en") {
|
|
225
|
+
console.error(t.spracheFalsch(erkennung));
|
|
226
|
+
return 1;
|
|
227
|
+
}
|
|
228
|
+
const ort = ortHier(new Date());
|
|
229
|
+
const ordner = fertigerOrdner(t, woerter[0], ort);
|
|
230
|
+
if (ordner === null)
|
|
231
|
+
return 1;
|
|
232
|
+
const p = aufnehmerPython();
|
|
233
|
+
if (p === null) {
|
|
234
|
+
console.error(t.nichtEingerichtet);
|
|
235
|
+
return 1;
|
|
236
|
+
}
|
|
237
|
+
console.log(t.werteAus);
|
|
238
|
+
const r = rufeWerkzeug(p, ["zeitleiste", ordner, erkennung, ...(ohne.gesetzt ? ["--ohne-getipptes"] : [])], ordner, null);
|
|
239
|
+
const d = r.ok ? r.daten : {};
|
|
240
|
+
const datei = join(ordner, "zeitleiste.md"); // kein-anzeigetext: Dateiname
|
|
241
|
+
if (d.ergebnis === "fertig" && istZahl(d.dauer_sek) && [d.saetze, d.klicks, d.tippstellen, d.passwortfelder].every(istZaehlung) && existsSync(datei)) {
|
|
242
|
+
const n = (w) => w;
|
|
243
|
+
console.log(t.zeitleisteFertig(dauerText(d.dauer_sek), n(d.saetze), n(d.klicks), n(d.tippstellen), n(d.passwortfelder), zeigeOrdner(datei, ort)));
|
|
244
|
+
return 0;
|
|
245
|
+
}
|
|
246
|
+
if (d.ergebnis === "keine-sprache")
|
|
247
|
+
console.error(t.keineSprache);
|
|
248
|
+
else if (d.ergebnis === "kein-ton")
|
|
249
|
+
console.error(t.keinTon);
|
|
250
|
+
else
|
|
251
|
+
console.error(t.zeitleisteFehler(grundSatz(t, d.grund)));
|
|
252
|
+
return 1;
|
|
253
|
+
}
|
|
254
|
+
export async function zeigenBild(rest) {
|
|
255
|
+
const lage = vorab(rest, (t) => t.helpBild);
|
|
256
|
+
if (typeof lage === "number")
|
|
257
|
+
return lage;
|
|
258
|
+
const { s, t } = lage;
|
|
259
|
+
const { woerter, optionen } = trenneNamenUndOptionen(rest);
|
|
260
|
+
const z = zerlege([...woerter.slice(2), ...optionen], [], s);
|
|
261
|
+
if (!z.ok || woerter.length === 0) {
|
|
262
|
+
console.error(`${z.ok ? "" : `${z.meldung}\n`}${t.helpBild}`);
|
|
263
|
+
return 1;
|
|
264
|
+
}
|
|
265
|
+
const sekunden = zeitAusText(woerter[0]);
|
|
266
|
+
if (sekunden === null) {
|
|
267
|
+
console.error(t.zeitFalsch(woerter[0]));
|
|
268
|
+
return 1;
|
|
269
|
+
}
|
|
270
|
+
const ort = ortHier(new Date());
|
|
271
|
+
const ordner = fertigerOrdner(t, woerter[1], ort);
|
|
272
|
+
if (ordner === null)
|
|
273
|
+
return 1;
|
|
274
|
+
const p = aufnehmerPython();
|
|
275
|
+
if (p === null) {
|
|
276
|
+
console.error(t.nichtEingerichtet);
|
|
277
|
+
return 1;
|
|
278
|
+
}
|
|
279
|
+
const r = rufeWerkzeug(p, ["bild", ordner, String(sekunden)], ordner, null);
|
|
280
|
+
// Der Pfad wird hier gebaut, nicht vom Werkzeug uebernommen -- und muss danach wirklich daliegen.
|
|
281
|
+
const datei = join(ordner, "bilder", bildName(sekunden)); // kein-anzeigetext: Ordnername
|
|
282
|
+
if (r.ok && r.daten.ergebnis === "fertig" && existsSync(datei)) {
|
|
283
|
+
console.log(t.bildFertig(datei));
|
|
284
|
+
return 0;
|
|
285
|
+
}
|
|
286
|
+
console.error(t.bildFehler(grundSatz(t, r.ok ? r.daten.grund : null)));
|
|
287
|
+
return 1;
|
|
288
|
+
}
|