kimal3 0.1.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 +74 -0
- package/dist/anmeldung.js +55 -0
- package/dist/api.js +51 -0
- package/dist/argumente.js +39 -0
- package/dist/befehle/login.js +66 -0
- package/dist/befehle/logout.js +33 -0
- package/dist/befehle/projekt-liste.js +53 -0
- package/dist/befehle/projekt-neu.js +82 -0
- package/dist/befehle/skill-install.js +44 -0
- package/dist/kimal3.js +81 -0
- package/package.json +13 -0
- package/skill/SKILL.md +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# kimal3
|
|
2
|
+
|
|
3
|
+
**Trag deine KI-Projekte dort ein, wo sie entstehen: im Terminal.**
|
|
4
|
+
|
|
5
|
+
Wer mit Claude Code arbeitet, baut echte Dinge — und dokumentiert sie hinterher nicht,
|
|
6
|
+
weil das ein zweiter Arbeitsschritt in einem anderen Fenster wäre. `kimal3` schließt
|
|
7
|
+
diese Lücke: Am Ende einer Sitzung trägt Claude das fertige Projekt selbst in dein
|
|
8
|
+
KImal3-Erfolgsprofil ein. Du gibst es dort nur noch frei.
|
|
9
|
+
|
|
10
|
+
> **Voraussetzung: ein KImal3-Konto mit Pro.**
|
|
11
|
+
> Ohne Pro meldet sich dieses Werkzeug ab, ohne etwas zu tun.
|
|
12
|
+
> Was Pro enthält und wie du es bekommst: **https://kimal3-app.vercel.app/pro**
|
|
13
|
+
|
|
14
|
+
## Loslegen
|
|
15
|
+
|
|
16
|
+
Zwei Befehle, einmal pro Rechner:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx kimal3 login # diesen Rechner mit deinem Konto verbinden
|
|
20
|
+
npx kimal3 skill install # Claude beibringen, das Werkzeug zu benutzen
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`login` zeigt dir einen achtstelligen Code und öffnet den Browser. Dort bestätigst du
|
|
24
|
+
ihn in deinem eingeloggten Konto — so wie bei `gh auth login`. **Es wird kein Passwort
|
|
25
|
+
ins Terminal getippt.**
|
|
26
|
+
|
|
27
|
+
## Befehle
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
kimal3 Zeigt, ob dieser Rechner angemeldet ist
|
|
31
|
+
kimal3 login Diesen Rechner mit deinem Konto verbinden
|
|
32
|
+
kimal3 logout Verbindung wieder aufheben
|
|
33
|
+
kimal3 projekt neu … Ein Projekt ins Erfolgsprofil eintragen
|
|
34
|
+
kimal3 projekt liste Deine eingetragenen Projekte anzeigen
|
|
35
|
+
kimal3 skill install Claude beibringen, dieses Werkzeug zu benutzen
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Mehr zu einem Befehl: `kimal3 projekt neu --help`
|
|
39
|
+
|
|
40
|
+
## Was eingetragen wird
|
|
41
|
+
|
|
42
|
+
Nur, was du eingibst — Titel, Beschreibung, optional ein Link. **Dein Code wird nicht
|
|
43
|
+
gelesen, nicht hochgeladen und nicht analysiert.** Das Werkzeug schreibt ausschließlich
|
|
44
|
+
über die KImal3-App und hat keinen eigenen Datenbankzugang.
|
|
45
|
+
|
|
46
|
+
Eingetragene Projekte sind zunächst **nicht öffentlich**. Sie erscheinen in deinem
|
|
47
|
+
Erfolgsprofil, und du entscheidest dort, was sichtbar wird.
|
|
48
|
+
|
|
49
|
+
## Rückgabewerte
|
|
50
|
+
|
|
51
|
+
Für Skripte und für Claude selbst:
|
|
52
|
+
|
|
53
|
+
| Wert | Bedeutung | Hilft |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| `0` | hat geklappt | — |
|
|
56
|
+
| `1` | Eingabe unvollständig oder nichts gefunden | Eingabe prüfen |
|
|
57
|
+
| `2` | nicht angemeldet | `kimal3 login` |
|
|
58
|
+
| `3` | gerade nicht erreichbar | später noch einmal |
|
|
59
|
+
| `4` | kein Pro-Konto | erneutes Versuchen hilft **nicht** |
|
|
60
|
+
|
|
61
|
+
Der Unterschied zwischen `3` und `4` ist Absicht: Bei `3` lohnt ein zweiter Versuch,
|
|
62
|
+
bei `4` nie.
|
|
63
|
+
|
|
64
|
+
## Voraussetzungen
|
|
65
|
+
|
|
66
|
+
Node.js 20 oder neuer. Sonst nichts — das Werkzeug bringt **keine fremden Bibliotheken**
|
|
67
|
+
mit.
|
|
68
|
+
|
|
69
|
+
## Lizenz
|
|
70
|
+
|
|
71
|
+
Proprietär. Die Nutzung ist Inhabern eines gültigen KImal3-Pro-Kontos gestattet;
|
|
72
|
+
Weitergabe, Veränderung und Weiterverwendung des Codes sind nicht erlaubt.
|
|
73
|
+
|
|
74
|
+
© KImal3 · https://kimal3.de
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { chmodSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Wo die App wohnt. Ueberschreibbar fuers lokale Entwickeln:
|
|
6
|
+
* KIMAL3_URL=http://localhost:3000 npx kimal3 projekt liste
|
|
7
|
+
*/
|
|
8
|
+
export const BASIS_URL = process.env.KIMAL3_URL ?? "https://kimal3-app.vercel.app";
|
|
9
|
+
const ORDNER = join(homedir(), ".kimal3");
|
|
10
|
+
const DATEI = join(ORDNER, "anmeldung.json");
|
|
11
|
+
export function leseAnmeldung() {
|
|
12
|
+
try {
|
|
13
|
+
const roh = JSON.parse(readFileSync(DATEI, "utf8"));
|
|
14
|
+
if (roh && typeof roh === "object" && "schluessel" in roh && "email" in roh) {
|
|
15
|
+
const a = roh;
|
|
16
|
+
if (typeof a.schluessel === "string" && typeof a.email === "string") {
|
|
17
|
+
return { schluessel: a.schluessel, email: a.email };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* ⚠️ `chmod 0600` wirkt auf Mac/Linux. Auf Windows ist die Datei durch das
|
|
28
|
+
* Nutzerprofil geschuetzt, nicht durch diese Zeile — dort ist sie wirkungslos,
|
|
29
|
+
* aber auch nicht schaedlich. Beides gehoert in den Handtest.
|
|
30
|
+
*
|
|
31
|
+
* 🔑 Die Rechte stehen im `mode` der ERZEUGUNG, nicht in einem `chmod`
|
|
32
|
+
* danach (Review-Befund 2026-08-13): `writeFileSync` legt die Datei sonst mit
|
|
33
|
+
* 0644 an, und bis der `chmod` greift, ist der Ausweis auf einem
|
|
34
|
+
* Mehrbenutzer-Linux fuer alle lesbar. Ein Fenster von Millisekunden ist ein
|
|
35
|
+
* Fenster. Der `chmod` bleibt trotzdem stehen — er greift auch dann, wenn die
|
|
36
|
+
* Datei schon existierte und der `mode` deshalb ignoriert wird.
|
|
37
|
+
*/
|
|
38
|
+
export function schreibeAnmeldung(a) {
|
|
39
|
+
mkdirSync(ORDNER, { recursive: true, mode: 0o700 });
|
|
40
|
+
writeFileSync(DATEI, JSON.stringify(a, null, 2), { encoding: "utf8", mode: 0o600 });
|
|
41
|
+
try {
|
|
42
|
+
chmodSync(DATEI, 0o600);
|
|
43
|
+
}
|
|
44
|
+
catch { /* Windows */ }
|
|
45
|
+
}
|
|
46
|
+
export function loescheAnmeldung() {
|
|
47
|
+
try {
|
|
48
|
+
rmSync(DATEI);
|
|
49
|
+
}
|
|
50
|
+
catch { /* war schon weg */ }
|
|
51
|
+
}
|
|
52
|
+
/** Nur fuer Meldungen an den Menschen — damit er weiss, wo die Datei liegt. */
|
|
53
|
+
export function anmeldungsPfad() {
|
|
54
|
+
return DATEI;
|
|
55
|
+
}
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { BASIS_URL, leseAnmeldung } from "./anmeldung.js";
|
|
2
|
+
export class NichtAngemeldet extends Error {
|
|
3
|
+
constructor() { super("nicht angemeldet"); }
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Ein Aufruf gegen die App. `mitAusweis` haengt den Schluessel an.
|
|
7
|
+
*
|
|
8
|
+
* Netzfehler werden NICHT zu einem Status 0 verbogen — sie werfen, und der
|
|
9
|
+
* Verteiler in kimal3.ts macht daraus Rueckgabewert 3. Ein Netzfehler ist
|
|
10
|
+
* etwas anderes als eine Absage vom Server, und wer beides gleich behandelt,
|
|
11
|
+
* schickt Menschen zur falschen Fehlersuche.
|
|
12
|
+
*/
|
|
13
|
+
export async function ruf(pfad, optionen = {}) {
|
|
14
|
+
const kopf = {};
|
|
15
|
+
if (optionen.mitAusweis) {
|
|
16
|
+
const a = leseAnmeldung();
|
|
17
|
+
if (!a)
|
|
18
|
+
throw new NichtAngemeldet();
|
|
19
|
+
kopf.Authorization = `Bearer ${a.schluessel}`;
|
|
20
|
+
}
|
|
21
|
+
const methode = optionen.methode ?? "POST";
|
|
22
|
+
// Content-Type nur, wenn wirklich etwas im Rumpf steht. Ein GET mit
|
|
23
|
+
// "Content-Type: application/json" und ohne Rumpf ist eine Luege ueber die
|
|
24
|
+
// eigene Anfrage — manche Server nehmen das uebel.
|
|
25
|
+
if (methode === "POST")
|
|
26
|
+
kopf["Content-Type"] = "application/json";
|
|
27
|
+
const antwort = await fetch(`${BASIS_URL}${pfad}`, {
|
|
28
|
+
method: methode,
|
|
29
|
+
headers: kopf,
|
|
30
|
+
body: methode === "POST" ? JSON.stringify(optionen.koerper ?? {}) : undefined,
|
|
31
|
+
});
|
|
32
|
+
return { status: antwort.status, koerper: await antwort.json().catch(() => null) };
|
|
33
|
+
}
|
|
34
|
+
/** Holt ein Textfeld aus einer Antwort, ohne `any`. */
|
|
35
|
+
export function feld(koerper, name) {
|
|
36
|
+
if (koerper && typeof koerper === "object" && name in koerper) {
|
|
37
|
+
const w = koerper[name];
|
|
38
|
+
if (typeof w === "string")
|
|
39
|
+
return w;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
/** Dasselbe fuer eine Liste von Texten (fuer `projekt liste`). */
|
|
44
|
+
export function textListe(koerper, name) {
|
|
45
|
+
if (koerper && typeof koerper === "object" && name in koerper) {
|
|
46
|
+
const w = koerper[name];
|
|
47
|
+
if (Array.isArray(w) && w.every((x) => typeof x === "string"))
|
|
48
|
+
return w;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Die Befehlszeile zerlegen — von Hand, weil `cli/` keine fremden
|
|
3
|
+
* Bibliotheken hat (Entscheidung aus Abschnitt A der Spec).
|
|
4
|
+
*
|
|
5
|
+
* Bewusst streng: Eine unbekannte Option ist ein FEHLER, kein stilles
|
|
6
|
+
* Uebergehen. Wer `--titl` tippt, soll es erfahren, statt ein Projekt ohne
|
|
7
|
+
* Titel anzulegen.
|
|
8
|
+
*/
|
|
9
|
+
export function zerlege(argv, erlaubt) {
|
|
10
|
+
const befehl = [];
|
|
11
|
+
const optionen = {};
|
|
12
|
+
for (let i = 0; i < argv.length; i++) {
|
|
13
|
+
const teil = argv[i];
|
|
14
|
+
if (!teil.startsWith("--")) {
|
|
15
|
+
if (Object.keys(optionen).length > 0) {
|
|
16
|
+
return { ok: false, meldung: `Nach Optionen kommt kein Befehlswort mehr: „${teil}".` };
|
|
17
|
+
}
|
|
18
|
+
befehl.push(teil);
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const gleich = teil.indexOf("=");
|
|
22
|
+
const name = gleich === -1 ? teil.slice(2) : teil.slice(2, gleich);
|
|
23
|
+
if (!erlaubt.includes(name)) {
|
|
24
|
+
return { ok: false, meldung: `Die Option „--${name}" kenne ich nicht.` };
|
|
25
|
+
}
|
|
26
|
+
if (gleich !== -1) {
|
|
27
|
+
optionen[name] = teil.slice(gleich + 1);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const wert = argv[i + 1];
|
|
31
|
+
// Eine Zahl mit Minus ist ein Wert; "--irgendwas" ist die naechste Option.
|
|
32
|
+
if (wert === undefined || wert.startsWith("--")) {
|
|
33
|
+
return { ok: false, meldung: `Der Option „--${name}" fehlt ihr Wert.` };
|
|
34
|
+
}
|
|
35
|
+
optionen[name] = wert;
|
|
36
|
+
i++;
|
|
37
|
+
}
|
|
38
|
+
return { ok: true, werte: { befehl, optionen } };
|
|
39
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { hostname } from "node:os";
|
|
2
|
+
import { feld, ruf } from "../api.js";
|
|
3
|
+
import { BASIS_URL, schreibeAnmeldung } from "../anmeldung.js";
|
|
4
|
+
const warte = (ms) => new Promise((f) => setTimeout(f, ms));
|
|
5
|
+
export async function login() {
|
|
6
|
+
const start = await ruf("/api/cli/login/start", { koerper: { bezeichnung: hostname() } });
|
|
7
|
+
const nutzerCode = feld(start.koerper, "nutzer_code");
|
|
8
|
+
const geraeteCode = feld(start.koerper, "geraete_code");
|
|
9
|
+
if (start.status !== 200 || !nutzerCode || !geraeteCode) {
|
|
10
|
+
console.error("kimal3 ist gerade nicht erreichbar.");
|
|
11
|
+
return 3;
|
|
12
|
+
}
|
|
13
|
+
console.log(`\n Öffne ${BASIS_URL}/cli`);
|
|
14
|
+
console.log(` und gib diesen Code ein: ${nutzerCode}\n`);
|
|
15
|
+
console.log(" Warte auf die Bestätigung … (Abbruch mit Strg+C)");
|
|
16
|
+
const endeUm = Date.now() + 10 * 60 * 1000;
|
|
17
|
+
while (Date.now() < endeUm) {
|
|
18
|
+
await warte(2000);
|
|
19
|
+
// ⚠️ Diese Schleife laeuft per Definition zehn Minuten ueber ein Netz.
|
|
20
|
+
// Ein einzelner Aussetzer — Vercel-Kaltstart, kurzes 502, WLAN-Huepfer —
|
|
21
|
+
// darf die ganze Anmeldung nicht wegwerfen, waehrend der Mensch im
|
|
22
|
+
// Browser vielleicht gerade bestaetigt. Deshalb: weiterfragen statt
|
|
23
|
+
// abbrechen. Beendet wird nur bei einer EINDEUTIGEN Antwort.
|
|
24
|
+
// (Review-Befund 2026-08-13.)
|
|
25
|
+
let a;
|
|
26
|
+
try {
|
|
27
|
+
a = await ruf("/api/cli/login/abholen", { koerper: { geraete_code: geraeteCode } });
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (a.status === 202)
|
|
33
|
+
continue;
|
|
34
|
+
if (a.status >= 500)
|
|
35
|
+
continue;
|
|
36
|
+
if (a.status === 410) {
|
|
37
|
+
console.error("\nDer Anmeldecode ist abgelaufen. Bitte `kimal3 login` noch einmal.");
|
|
38
|
+
return 2;
|
|
39
|
+
}
|
|
40
|
+
// 🔴 Dieser Zweig MUSS hier oben stehen. Ohne ihn faellt eine 402 durch bis
|
|
41
|
+
// ans Ende und meldet "Das hat nicht geklappt." mit Rueckgabewert 3 --
|
|
42
|
+
// Genau die nichtssagende Meldung, die diese Schranke vermeiden soll: Der
|
|
43
|
+
// Mensch erfaehrt nicht, dass ihm ein Konto fehlt, sondern glaubt an einen
|
|
44
|
+
// Serverfehler.
|
|
45
|
+
//
|
|
46
|
+
// 🔑 Und sie kommt hier nur bei einer ECHTEN Absage an: Liess sich der Plan
|
|
47
|
+
// im Browser gar nicht nachsehen, bleibt der Zettel auf "wartet" — diese
|
|
48
|
+
// Schleife fragt also einfach weiter, statt aufzugeben. Genau dafuer ist
|
|
49
|
+
// sie da. (Review-Befund 2026-08-14, s. `src/app/cli/actions.ts`.)
|
|
50
|
+
if (a.status === 402) {
|
|
51
|
+
console.error(`\n${feld(a.koerper, "meldung") ?? "Dafür brauchst du ein Pro-Konto."}`);
|
|
52
|
+
return 4;
|
|
53
|
+
}
|
|
54
|
+
const schluessel = feld(a.koerper, "schluessel");
|
|
55
|
+
const email = feld(a.koerper, "email");
|
|
56
|
+
if (a.status === 200 && schluessel && email) {
|
|
57
|
+
schreibeAnmeldung({ schluessel, email });
|
|
58
|
+
console.log(`\n✓ Angemeldet als ${email}.`);
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
console.error("\nDas hat nicht geklappt.");
|
|
62
|
+
return 3;
|
|
63
|
+
}
|
|
64
|
+
console.error("\nDer Anmeldecode ist abgelaufen. Bitte `kimal3 login` noch einmal.");
|
|
65
|
+
return 2;
|
|
66
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { leseAnmeldung, loescheAnmeldung } from "../anmeldung.js";
|
|
2
|
+
import { ruf } from "../api.js";
|
|
3
|
+
/**
|
|
4
|
+
* Abmelden — erst beim Server widerrufen, DANN die Datei loeschen.
|
|
5
|
+
*
|
|
6
|
+
* ⚠️ Die Reihenfolge ist der ganze Punkt: Loescht man zuerst, kennt niemand
|
|
7
|
+
* mehr den Schluessel, den man gerade widerrufen wollte. Er bliebe auf dem
|
|
8
|
+
* Server gueltig, und nichts wuerde davon berichten.
|
|
9
|
+
*/
|
|
10
|
+
export async function logout() {
|
|
11
|
+
const a = leseAnmeldung();
|
|
12
|
+
if (!a) {
|
|
13
|
+
// Kein Fehler: Der Zielzustand ist bereits erreicht. Deshalb 0.
|
|
14
|
+
console.log("Auf diesem Rechner war keine Anmeldung.");
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const antwort = await ruf("/api/cli/logout", { mitAusweis: true });
|
|
19
|
+
if (antwort.status !== 200 && antwort.status !== 401) {
|
|
20
|
+
console.error("kimal3 ist gerade nicht erreichbar. Es wurde nichts geändert.");
|
|
21
|
+
return 3;
|
|
22
|
+
}
|
|
23
|
+
// 401 heisst: Der Schluessel gilt ohnehin nicht mehr (schon widerrufen
|
|
24
|
+
// oder abgelaufen). Der Zielzustand stimmt also — die Datei darf weg.
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
console.error("kimal3 ist gerade nicht erreichbar. Es wurde nichts geändert.");
|
|
28
|
+
return 3;
|
|
29
|
+
}
|
|
30
|
+
loescheAnmeldung();
|
|
31
|
+
console.log(`Abgemeldet. (${a.email})`);
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { feld, ruf, textListe } from "../api.js";
|
|
2
|
+
import { BASIS_URL } from "../anmeldung.js";
|
|
3
|
+
/**
|
|
4
|
+
* Die eigenen Projekte anzeigen.
|
|
5
|
+
*
|
|
6
|
+
* 🔑 Wofuer das da ist: Damit Claude VOR dem Eintragen nachsehen kann. Ohne
|
|
7
|
+
* diesen Befehl steht dasselbe Projekt irgendwann zweimal auf einer
|
|
8
|
+
* oeffentlichen Seite mit dem Namen eines Menschen darauf.
|
|
9
|
+
*/
|
|
10
|
+
export async function projektListe() {
|
|
11
|
+
const antwort = await ruf("/api/cli/projekt", { methode: "GET", mitAusweis: true });
|
|
12
|
+
if (antwort.status === 200) {
|
|
13
|
+
const titel = textListe(antwort.koerper, "titel");
|
|
14
|
+
if (!titel) {
|
|
15
|
+
console.error("Das hat nicht geklappt.");
|
|
16
|
+
return 3;
|
|
17
|
+
}
|
|
18
|
+
// Eine leere Liste ist KEIN Fehler — sie ist eine gueltige Antwort auf
|
|
19
|
+
// eine gueltige Frage. Deshalb 0 und ein Satz, kein Fehlertext.
|
|
20
|
+
if (titel.length === 0) {
|
|
21
|
+
console.log("Du hast noch keine Projekte eingetragen.");
|
|
22
|
+
console.log(` Eintragen: kimal3 projekt neu --titel "…"`);
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
console.log(`\n Deine Projekte (${titel.length} von 12):`);
|
|
26
|
+
titel.forEach((t, i) => console.log(` ${i + 1}. ${t}`));
|
|
27
|
+
console.log(`\n Ansehen: ${BASIS_URL}/profil\n`);
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
const meldung = feld(antwort.koerper, "meldung") ?? "Das hat nicht geklappt.";
|
|
31
|
+
// 402 vor 401: Beide heissen "kommst hier nicht rein", verlangen aber
|
|
32
|
+
// Gegenteiliges. Wer sie zusammenwirft, raet dem Menschen `kimal3 login` —
|
|
33
|
+
// und der meldet sich dann erfolgreich an, ohne dass es etwas aendert.
|
|
34
|
+
if (antwort.status === 402) {
|
|
35
|
+
console.error(meldung);
|
|
36
|
+
return 4;
|
|
37
|
+
}
|
|
38
|
+
if (antwort.status === 401) {
|
|
39
|
+
console.error(`${meldung} Bitte \`kimal3 login\` ausführen.`);
|
|
40
|
+
return 2;
|
|
41
|
+
}
|
|
42
|
+
if (antwort.status === 409 && feld(antwort.koerper, "grund") === "kein-profil") {
|
|
43
|
+
console.error(`${meldung} Leg es hier an: ${BASIS_URL}/profil`);
|
|
44
|
+
return 1;
|
|
45
|
+
}
|
|
46
|
+
// ⚠️ Hier landet auch die `503` "Konto gerade nicht prüfbar" — und sie MUSS
|
|
47
|
+
// hier landen, nicht oben bei der 402. Rueckgabewert 3 heisst "nochmal
|
|
48
|
+
// versuchen kann helfen"; 4 heisst laut SKILL.md ausdruecklich "nicht
|
|
49
|
+
// wiederholen". Ein Lesefehler an der Pro-Schranke gehoert in den ersten
|
|
50
|
+
// Topf. (Review-Befund 2026-08-14.)
|
|
51
|
+
console.error(meldung);
|
|
52
|
+
return antwort.status >= 500 ? 3 : 1;
|
|
53
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { feld, ruf } from "../api.js";
|
|
2
|
+
import { BASIS_URL } from "../anmeldung.js";
|
|
3
|
+
import { zerlege } from "../argumente.js";
|
|
4
|
+
const OPTIONEN = [
|
|
5
|
+
"titel", "beschreibung", "symbol", "stunden", "ergebnis", "umsatz",
|
|
6
|
+
"wirkung", "wirkung-label", "link", "live-seit",
|
|
7
|
+
];
|
|
8
|
+
const HILFE = `
|
|
9
|
+
kimal3 projekt neu --titel "Angebots-Generator" [Optionen]
|
|
10
|
+
|
|
11
|
+
--titel Pflicht. 2–80 Zeichen.
|
|
12
|
+
--beschreibung Bis 300 Zeichen.
|
|
13
|
+
--symbol Eines der bekannten Symbole, z. B. bot, mail, workflow.
|
|
14
|
+
--stunden Zeitersparnis pro Woche, 0–168.
|
|
15
|
+
--ergebnis Ergebnis pro Monat in Euro, z. B. 1200 oder 1200,50.
|
|
16
|
+
--umsatz Umsatz pro Monat in Euro.
|
|
17
|
+
--wirkung Kurz, bis 20 Zeichen, z. B. "-70%".
|
|
18
|
+
--wirkung-label Wozu, bis 40 Zeichen, z. B. "Angebotszeit".
|
|
19
|
+
--link Vollstaendige Adresse mit http:// oder https://.
|
|
20
|
+
--live-seit Datum als JJJJ-MM-TT.
|
|
21
|
+
`;
|
|
22
|
+
export async function projektNeu(argv) {
|
|
23
|
+
// ⚠️ VOR dem Zerlegen abfragen, nicht danach.
|
|
24
|
+
// Der Bauplan hatte "help" in der Optionsliste und fragte hinterher. Das
|
|
25
|
+
// konnte nie greifen: `zerlege` verlangt zu jeder Option einen Wert, also
|
|
26
|
+
// scheitert `--help` schon dort mit "fehlt ihr Wert". Ein Schalter ist keine
|
|
27
|
+
// Option mit Wert — er gehoert vor den Zerleger.
|
|
28
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
29
|
+
console.log(HILFE);
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
const z = zerlege(argv, OPTIONEN);
|
|
33
|
+
if (!z.ok) {
|
|
34
|
+
console.error(z.meldung);
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
const o = z.werte.optionen;
|
|
38
|
+
if (!o.titel) {
|
|
39
|
+
console.error("Es fehlt --titel.\n" + HILFE);
|
|
40
|
+
return 1;
|
|
41
|
+
}
|
|
42
|
+
const antwort = await ruf("/api/cli/projekt", {
|
|
43
|
+
mitAusweis: true,
|
|
44
|
+
koerper: {
|
|
45
|
+
titel: o.titel, beschreibung: o.beschreibung ?? "", icon: o.symbol ?? "",
|
|
46
|
+
wirkung_text: o.wirkung ?? "", wirkung_label: o["wirkung-label"] ?? "",
|
|
47
|
+
zeitersparnis_std_wo: o.stunden ?? "", ergebnis_monat_eur: o.ergebnis ?? "",
|
|
48
|
+
umsatz_monat_eur: o.umsatz ?? "", link_url: o.link ?? "",
|
|
49
|
+
live_seit: o["live-seit"] ?? "",
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
if (antwort.status === 201) {
|
|
53
|
+
const titel = feld(antwort.koerper, "titel") ?? o.titel;
|
|
54
|
+
const anzahl = antwort.koerper?.anzahl;
|
|
55
|
+
console.log(`✓ „${titel}" ins Erfolgsprofil eingetragen (Projekt ${anzahl} von 12).`);
|
|
56
|
+
console.log(` Ansehen: ${BASIS_URL}/profil`);
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
const meldung = feld(antwort.koerper, "meldung") ?? "Das hat nicht geklappt.";
|
|
60
|
+
// 402 vor 401: Beide heissen "kommst hier nicht rein", verlangen aber
|
|
61
|
+
// Gegenteiliges. Wer sie zusammenwirft, raet dem Menschen `kimal3 login` —
|
|
62
|
+
// und der meldet sich dann erfolgreich an, ohne dass es etwas aendert.
|
|
63
|
+
if (antwort.status === 402) {
|
|
64
|
+
console.error(meldung);
|
|
65
|
+
return 4;
|
|
66
|
+
}
|
|
67
|
+
if (antwort.status === 401) {
|
|
68
|
+
console.error(`${meldung} Bitte \`kimal3 login\` ausführen.`);
|
|
69
|
+
return 2;
|
|
70
|
+
}
|
|
71
|
+
if (antwort.status === 409 && feld(antwort.koerper, "grund") === "kein-profil") {
|
|
72
|
+
console.error(`${meldung} Leg es hier an: ${BASIS_URL}/profil`);
|
|
73
|
+
return 1;
|
|
74
|
+
}
|
|
75
|
+
// ⚠️ Hier landet auch die `503` "Konto gerade nicht prüfbar" — und sie MUSS
|
|
76
|
+
// hier landen, nicht oben bei der 402. Rueckgabewert 3 heisst "nochmal
|
|
77
|
+
// versuchen kann helfen"; 4 heisst laut SKILL.md ausdruecklich "nicht
|
|
78
|
+
// wiederholen". Ein Lesefehler an der Pro-Schranke gehoert in den ersten
|
|
79
|
+
// Topf. (Review-Befund 2026-08-14.)
|
|
80
|
+
console.error(meldung);
|
|
81
|
+
return antwort.status >= 500 ? 3 : 1;
|
|
82
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
/**
|
|
6
|
+
* Legt das Skill GLOBAL ab, damit Claude dieses Werkzeug ueberall kennt.
|
|
7
|
+
*
|
|
8
|
+
* Der Pfad zur eigenen Datei kommt ueber `import.meta.url` und NICHT ueber
|
|
9
|
+
* `process.cwd()` — die Quelle liegt im installierten Paket, das Ziel im
|
|
10
|
+
* Ordner des Menschen. Wer beides verwechselt, kopiert eine Datei auf sich
|
|
11
|
+
* selbst.
|
|
12
|
+
*
|
|
13
|
+
* `dist/befehle/skill-install.js` -> zwei Ebenen hoch -> `skill/SKILL.md`.
|
|
14
|
+
*/
|
|
15
|
+
export async function skillInstall() {
|
|
16
|
+
const quelle = fileURLToPath(new URL("../../skill/SKILL.md", import.meta.url));
|
|
17
|
+
if (!existsSync(quelle)) {
|
|
18
|
+
console.error("Das Skill liegt nicht im Paket. Bitte kimal3 neu installieren.");
|
|
19
|
+
return 3;
|
|
20
|
+
}
|
|
21
|
+
// 🔑 GLOBAL, nicht projektlokal (Fund von Dennis, 2026-08-13).
|
|
22
|
+
// Gemessen: In ~/.claude/skills liegen 34 Skills, im Projekt selbst kein
|
|
23
|
+
// einziges. Inhaltlich stimmt es auch -- "trag ein fertiges Projekt ins
|
|
24
|
+
// Erfolgsprofil ein" gilt in JEDEM Ordner, nicht in einem. Wer projektlokal
|
|
25
|
+
// installiert, hat das Werkzeug im naechsten Projekt nicht -- und merkt es
|
|
26
|
+
// erst, wenn er dort vergeblich "fertig" sagt.
|
|
27
|
+
const zielOrdner = join(homedir(), ".claude", "skills", "kimal3");
|
|
28
|
+
const ziel = join(zielOrdner, "SKILL.md");
|
|
29
|
+
// Vorher merken: "angelegt" und "aktualisiert" sind zwei verschiedene
|
|
30
|
+
// Wirklichkeiten und bekommen deshalb zwei verschiedene Meldungen.
|
|
31
|
+
const gabEsSchon = existsSync(ziel);
|
|
32
|
+
try {
|
|
33
|
+
mkdirSync(zielOrdner, { recursive: true });
|
|
34
|
+
copyFileSync(quelle, ziel);
|
|
35
|
+
}
|
|
36
|
+
catch (fehler) {
|
|
37
|
+
console.error(`Das Skill liess sich nicht schreiben: ${String(fehler)}`);
|
|
38
|
+
return 3;
|
|
39
|
+
}
|
|
40
|
+
console.log(gabEsSchon ? "✓ Skill aktualisiert." : "✓ Skill angelegt.");
|
|
41
|
+
console.log(` ${ziel}`);
|
|
42
|
+
console.log(" Claude kennt das Werkzeug ab der nächsten Sitzung.");
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
package/dist/kimal3.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Der Einstieg. Jeder Befehl liefert eine Zahl zurueck, und die Zahl ist die
|
|
4
|
+
* eigentliche Schnittstelle: Claude liest sie, nicht den Text.
|
|
5
|
+
*
|
|
6
|
+
* 0 erledigt · 1 Eingabe passt nicht · 2 Anmeldung fehlt ·
|
|
7
|
+
* 3 Server nicht erreichbar · 4 angemeldet, aber kein Pro-Zugang
|
|
8
|
+
*
|
|
9
|
+
* 🔑 Warum die 4 eine eigene Zahl braucht: "nicht angemeldet" (2) und "kein
|
|
10
|
+
* Zugang" (4) klingen aehnlich, verlangen aber Gegenteiliges — bei 2 hilft
|
|
11
|
+
* `kimal3 login`, bei 4 hilft genau das nicht. Wer beide auf dieselbe Zahl
|
|
12
|
+
* legt, laesst Claude den falschen Rat geben.
|
|
13
|
+
*/
|
|
14
|
+
import { leseAnmeldung } from "./anmeldung.js";
|
|
15
|
+
import { NichtAngemeldet } from "./api.js";
|
|
16
|
+
import { login } from "./befehle/login.js";
|
|
17
|
+
import { logout } from "./befehle/logout.js";
|
|
18
|
+
import { projektListe } from "./befehle/projekt-liste.js";
|
|
19
|
+
import { projektNeu } from "./befehle/projekt-neu.js";
|
|
20
|
+
import { skillInstall } from "./befehle/skill-install.js";
|
|
21
|
+
const HILFE = `
|
|
22
|
+
kimal3 — trag deine KI-Projekte dort ein, wo sie entstehen.
|
|
23
|
+
|
|
24
|
+
kimal3 login Diesen Rechner mit deinem Konto verbinden
|
|
25
|
+
kimal3 logout Verbindung wieder aufheben
|
|
26
|
+
kimal3 projekt neu … Ein Projekt ins Erfolgsprofil eintragen
|
|
27
|
+
kimal3 projekt liste Deine eingetragenen Projekte anzeigen
|
|
28
|
+
kimal3 skill install Claude beibringen, dieses Werkzeug zu benutzen
|
|
29
|
+
|
|
30
|
+
Mehr zu einem Befehl: kimal3 projekt neu --help
|
|
31
|
+
`;
|
|
32
|
+
/**
|
|
33
|
+
* Hilfe plus Zustand.
|
|
34
|
+
*
|
|
35
|
+
* ⚠️ Bewusst KEIN automatischer Login (Frage von Dennis, 2026-08-13, am selben
|
|
36
|
+
* Abend nochmals bestaetigt): Wer `npx kimal3` tippt, um sich das Ding
|
|
37
|
+
* anzusehen, bekaeme sonst unvermittelt einen Anmelde-Vorgang samt Browser.
|
|
38
|
+
* Und was taete der zweite Aufruf, wenn man schon angemeldet ist? Es gaebe
|
|
39
|
+
* zwei Wege zum selben Ergebnis, einer davon unerwartet.
|
|
40
|
+
* *Ein Befehl darf nicht mehr tun, als sein Name verspricht.*
|
|
41
|
+
*
|
|
42
|
+
* ⚠️ Und KEIN Netzaufruf: Wer nur die Hilfe sehen will, soll nicht auf einen
|
|
43
|
+
* Server warten. Ob die Anmeldung noch gilt, klaert der naechste echte Befehl.
|
|
44
|
+
*/
|
|
45
|
+
function zustandsZeile() {
|
|
46
|
+
const a = leseAnmeldung();
|
|
47
|
+
return a
|
|
48
|
+
? ` ● Angemeldet als ${a.email}`
|
|
49
|
+
: " ● Nicht angemeldet. → kimal3 login";
|
|
50
|
+
}
|
|
51
|
+
async function haupt() {
|
|
52
|
+
const argv = process.argv.slice(2);
|
|
53
|
+
const wort = argv.join(" ");
|
|
54
|
+
if (argv.length === 0 || wort === "--help" || wort === "-h") {
|
|
55
|
+
console.log(HILFE);
|
|
56
|
+
console.log(zustandsZeile() + "\n");
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
if (wort === "login")
|
|
60
|
+
return login();
|
|
61
|
+
if (wort === "logout")
|
|
62
|
+
return logout();
|
|
63
|
+
if (argv[0] === "projekt" && argv[1] === "neu")
|
|
64
|
+
return projektNeu(argv.slice(2));
|
|
65
|
+
if (wort === "projekt liste")
|
|
66
|
+
return projektListe();
|
|
67
|
+
if (wort === "skill install")
|
|
68
|
+
return skillInstall();
|
|
69
|
+
console.error(`Den Befehl „${wort}" kenne ich nicht.\n${HILFE}`);
|
|
70
|
+
return 1;
|
|
71
|
+
}
|
|
72
|
+
haupt()
|
|
73
|
+
.then((code) => process.exit(code))
|
|
74
|
+
.catch((f) => {
|
|
75
|
+
if (f instanceof NichtAngemeldet) {
|
|
76
|
+
console.error("Bitte zuerst `kimal3 login` ausführen.");
|
|
77
|
+
process.exit(2);
|
|
78
|
+
}
|
|
79
|
+
console.error("kimal3 ist gerade nicht erreichbar.");
|
|
80
|
+
process.exit(3);
|
|
81
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kimal3",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Trag deine KI-Projekte dort ein, wo sie entstehen: im Terminal.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": { "kimal3": "dist/kimal3.js" },
|
|
7
|
+
"files": ["dist", "skill", "README.md"],
|
|
8
|
+
"engines": { "node": ">=20" },
|
|
9
|
+
"scripts": { "build": "tsc -p tsconfig.json" },
|
|
10
|
+
"keywords": ["kimal3", "cli"],
|
|
11
|
+
"homepage": "https://kimal3-app.vercel.app/pro",
|
|
12
|
+
"license": "SEE LICENSE IN README.md"
|
|
13
|
+
}
|
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kimal3
|
|
3
|
+
description: Use when the user has finished an AI project and wants it recorded in their kimal3 Erfolgsprofil, or asks to list/add projects there.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# kimal3 — Projekte ins Erfolgsprofil eintragen
|
|
7
|
+
|
|
8
|
+
## Wann
|
|
9
|
+
Sobald der Mensch sagt, ein Projekt sei fertig, oder ausdrücklich bittet, es einzutragen.
|
|
10
|
+
|
|
11
|
+
## Erst nachsehen, dann schreiben
|
|
12
|
+
`npx kimal3 projekt liste` — steht es schon da, **nicht** noch einmal eintragen.
|
|
13
|
+
|
|
14
|
+
## Eintragen
|
|
15
|
+
`npx kimal3 projekt neu --titel "…" [--stunden 6] [--ergebnis 1200] [--symbol bot]`
|
|
16
|
+
Alle Optionen: `npx kimal3 projekt neu --help`
|
|
17
|
+
|
|
18
|
+
## 🔑 Zahlen nie erfinden
|
|
19
|
+
Zeitersparnis, Umsatz und Ergebnis sind Behauptungen über die Wirklichkeit eines Menschen — und
|
|
20
|
+
sie stehen anschließend **öffentlich** auf einer Seite mit seinem Namen. Ist eine Zahl nicht
|
|
21
|
+
genannt worden: **fragen** oder das Feld weglassen. Niemals schätzen.
|
|
22
|
+
|
|
23
|
+
## Nicht angemeldet?
|
|
24
|
+
Dem Menschen `npx kimal3 login` zeigen und ihn selbst bestätigen lassen. Die Anmeldung **nie**
|
|
25
|
+
selbst versuchen — sie braucht einen Menschen, der im Browser bestätigt.
|
|
26
|
+
|
|
27
|
+
## Kein Pro-Zugang (Rückgabewert 4)
|
|
28
|
+
Die Meldung enthält bereits die Adresse. Dem Menschen **die Meldung zeigen** und den Befehl
|
|
29
|
+
**nicht wiederholen** — ein zweiter Versuch ändert nichts und wirkt wie ein Fehler im Werkzeug.
|
|
30
|
+
Auch **nicht** `kimal3 login` vorschlagen: Der Mensch *ist* angemeldet, ihm fehlt das Konto.
|
|
31
|
+
|
|
32
|
+
## Vorübergehende Störung (Rückgabewert 3)
|
|
33
|
+
Server nicht erreichbar — oder das Konto ließ sich gerade nicht prüfen. Das ist **kein**
|
|
34
|
+
endgültiges Nein: **einmal** wiederholen ist richtig. Klappt es wieder nicht, dem Menschen die
|
|
35
|
+
Meldung zeigen und es dabei belassen.
|
|
36
|
+
|
|
37
|
+
## Rückgabewerte
|
|
38
|
+
`0` erledigt · `1` Eingabe passt nicht · `2` Anmeldung fehlt · `3` vorübergehende Störung,
|
|
39
|
+
einmal wiederholen · `4` angemeldet, aber kein Pro-Zugang — **nicht** wiederholen
|