feat(cli): add subcommand dispatch, --version and stub commands
This commit is contained in:
1 parent
24dfe4d46c
commit
33b97a50ea
4 files changed
+439
No files matched your search
+259
@@ -0,0 +1,259 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Exit-Codes.
|
||||||
|
const (
|
||||||
|
exitOK = 0 // Erfolg (auch --help, --version)
|
||||||
|
exitFailure = 1 // Befehl fehlgeschlagen oder noch nicht implementiert
|
||||||
|
exitUsage = 2 // Falsche Benutzung (unbekannte Flags, kein Befehl)
|
||||||
|
)
|
||||||
|
|
||||||
|
// command ist ein Eintrag der Befehlstabelle. Die Tabelle ist bewusst
|
||||||
|
// datengetrieben: Spätere Tasks (z. B. die TUI-Befehle aus Task 09) ergänzen
|
||||||
|
// hier Einträge bzw. Register-Funktionen, ohne die Dispatch-Logik in run()
|
||||||
|
// anzufassen.
|
||||||
|
type command struct {
|
||||||
|
name string // Subcommand-Name (bindende CLI-Surface aus der Spec)
|
||||||
|
short string // Kurzbeschreibung für `talk --help`
|
||||||
|
args string // Positionsargumente-Hinweis für die Usage-Zeile
|
||||||
|
detail string // Beschreibung für `talk <Befehl> --help`
|
||||||
|
register func(fs *flag.FlagSet) // hängt die Flags des Befehls an den FlagSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// commands ist die vollständige Befehlstabelle. Die Namen sind Teil der
|
||||||
|
// bindenden CLI-Surface (Spec: „CLI-Surface (exakt)“) — nicht umbenennen.
|
||||||
|
var commands = []command{
|
||||||
|
{
|
||||||
|
name: "say",
|
||||||
|
short: "Text ansagen (Default-Befehl)",
|
||||||
|
args: `"Text"`,
|
||||||
|
detail: "Spricht den Text über die Provider-Kette aus. Ohne Text-Argument wird stdin gelesen. `talk \"Text\"` ist die Abkürzung für `talk say \"Text\"`.",
|
||||||
|
register: addSayFlags,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrap",
|
||||||
|
short: "Befehl ausführen und Ergebnis ansagen",
|
||||||
|
args: "-- <Befehl> [Argumente …]",
|
||||||
|
detail: "Führt den Befehl aus (stdin/stdout/stderr werden durchgereicht) und spricht danach Erfolg bzw. Misserfolg und Dauer.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mute",
|
||||||
|
short: "Ansagen dauerhaft stummschalten",
|
||||||
|
detail: "Aktiviert den persistenten Mute-State des Daemons (Datei im Config-Verzeichnis).",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unmute",
|
||||||
|
short: "Stummschaltung aufheben",
|
||||||
|
detail: "Hebt den persistenten Mute-State des Daemons wieder auf.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "status",
|
||||||
|
short: "Daemon-Status anzeigen",
|
||||||
|
detail: "Zeigt Queue, aktuelles Item, Mute-State, aktiven Provider und Daemon-Version.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cancel",
|
||||||
|
short: "Laufende und geplante Ansagen abbrechen",
|
||||||
|
detail: "Bricht die laufende Ansage ab und leert die Queue des Daemons.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "voices",
|
||||||
|
short: "Verfügbare Stimmen anzeigen",
|
||||||
|
detail: "Listet die Stimmen aller Provider (Defaults + Config-Overrides) als Tabelle.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test",
|
||||||
|
short: "Test-Ansage ausgeben",
|
||||||
|
detail: "Spricht eine feste Testphrase und zeigt den verwendeten Provider samt Latenz.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "doctor",
|
||||||
|
short: "Setup-Diagnose anzeigen",
|
||||||
|
detail: "Prüft Daemon, Audio-Sink, Provider-Keys und Config und berichtet zeilenweise mit ✔/✘/⚠.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "setup",
|
||||||
|
short: "Geführte Ersteinrichtung (Wizard)",
|
||||||
|
detail: "Interaktiver Wizard: Provider-Keys, Stimmen probehören, config.toml schreiben. Ohne TTY: plain.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// addSayFlags registriert die Flags des say-Befehls. Sie gelten auch für den
|
||||||
|
// Default-Pfad `talk "Text"` (gleiche Flag-Oberfläche, festgelegt in der
|
||||||
|
// Spec). Das Verhalten entsteht in Task 02 (direkter Pfad) bzw. Task 04
|
||||||
|
// (Daemon-Pfad).
|
||||||
|
func addSayFlags(fs *flag.FlagSet) {
|
||||||
|
fs.Bool("wait", false, "warten, bis die Ansage fertig abgespielt ist")
|
||||||
|
fs.Bool("now", false, "Ansage sofort ausgeben (springt die Warteschlange)")
|
||||||
|
fs.String("priority", "normal", "Priorität: low | normal | high")
|
||||||
|
fs.String("voice", "", "Stimme als provider-spezifische ID")
|
||||||
|
fs.String("lang", "", "Sprache: de | en (Default: automatische Erkennung)")
|
||||||
|
fs.String("provider", "", "Provider pinnen: elevenlabs | openai | edge | osbuiltin")
|
||||||
|
}
|
||||||
|
|
||||||
|
// run ist der Einstiegspunkt der CLI (Argumente ohne Programmnamen). Alle
|
||||||
|
// Ausgaben gehen auf die übergebenen Writer, damit run direkt und ohne
|
||||||
|
// Prozessstart testbar ist. Rückgabe ist der Exit-Code.
|
||||||
|
func run(argv []string, stdout, stderr io.Writer) int {
|
||||||
|
if len(argv) == 0 {
|
||||||
|
// Task 02 macht aus dem nackten Aufruf ggf. einen stdin-Leser
|
||||||
|
// (Text kommt dann über die Pipe); bis dahin: Benutzungsfehler.
|
||||||
|
writeUsage(stderr)
|
||||||
|
return exitUsage
|
||||||
|
}
|
||||||
|
|
||||||
|
if argv[0] == "help" {
|
||||||
|
if len(argv) > 1 {
|
||||||
|
if cmd, ok := lookupCommand(argv[1]); ok {
|
||||||
|
writeCommandHelp(cmd, stdout)
|
||||||
|
return exitOK
|
||||||
|
}
|
||||||
|
fmt.Fprintf(stderr, "talk: unbekannter Befehl %q\n", argv[1])
|
||||||
|
return exitUsage
|
||||||
|
}
|
||||||
|
writeUsage(stdout)
|
||||||
|
return exitOK
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd, ok := lookupCommand(argv[0]); ok {
|
||||||
|
return runCommand(cmd, argv[1:], stdout, stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kein bekannter Befehl: Default-Pfad `talk "Text"` (mappt auf say)
|
||||||
|
// bzw. Root-Flags (--version, --help). Führt aktuell auf den say-Stub.
|
||||||
|
return runDefault(argv, stdout, stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupCommand(name string) (command, bool) {
|
||||||
|
for _, cmd := range commands {
|
||||||
|
if cmd.name == name {
|
||||||
|
return cmd, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return command{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// newCommandFlagSet baut den FlagSet eines Befehls: eigene Flags plus ein
|
||||||
|
// explizites --help. Die Default-Usage wird stummgeschaltet, damit Hilfetext
|
||||||
|
// ausschließlich über writeCommandHelp (eine Quelle) ausgegeben wird.
|
||||||
|
func newCommandFlagSet(cmd command, out io.Writer) (*flag.FlagSet, *bool) {
|
||||||
|
fs := flag.NewFlagSet("talk "+cmd.name, flag.ContinueOnError)
|
||||||
|
fs.SetOutput(out)
|
||||||
|
fs.Usage = func() {}
|
||||||
|
if cmd.register != nil {
|
||||||
|
cmd.register(fs)
|
||||||
|
}
|
||||||
|
help := fs.Bool("help", false, "Hilfe zu diesem Befehl anzeigen")
|
||||||
|
return fs, help
|
||||||
|
}
|
||||||
|
|
||||||
|
// runCommand führt einen benannten Subcommand aus — in diesem Stand als Stub.
|
||||||
|
func runCommand(cmd command, args []string, stdout, stderr io.Writer) int {
|
||||||
|
fs, help := newCommandFlagSet(cmd, stderr)
|
||||||
|
if err := fs.Parse(args); err != nil {
|
||||||
|
if errors.Is(err, flag.ErrHelp) { // z. B. nacktes -h
|
||||||
|
writeCommandHelp(cmd, stdout)
|
||||||
|
return exitOK
|
||||||
|
}
|
||||||
|
// Die Fehlermeldung selbst hat der FlagSet bereits ausgegeben.
|
||||||
|
fmt.Fprintf(stderr, "Hinweis: `talk %s --help` zeigt die Verwendung.\n", cmd.name)
|
||||||
|
return exitUsage
|
||||||
|
}
|
||||||
|
if *help {
|
||||||
|
writeCommandHelp(cmd, stdout)
|
||||||
|
return exitOK
|
||||||
|
}
|
||||||
|
return notImplemented(cmd.name, stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// runDefault behandelt Aufrufe ohne bekannten Subcommand: `talk "Text"`,
|
||||||
|
// `talk --version`, `talk --help` sowie say-Flags vor dem Text.
|
||||||
|
func runDefault(argv []string, stdout, stderr io.Writer) int {
|
||||||
|
fs := flag.NewFlagSet("talk", flag.ContinueOnError)
|
||||||
|
fs.SetOutput(stderr)
|
||||||
|
fs.Usage = func() {}
|
||||||
|
showVersion := fs.Bool("version", false, "Version anzeigen")
|
||||||
|
help := fs.Bool("help", false, "diese Hilfe anzeigen")
|
||||||
|
addSayFlags(fs)
|
||||||
|
|
||||||
|
if err := fs.Parse(argv); err != nil {
|
||||||
|
if errors.Is(err, flag.ErrHelp) {
|
||||||
|
writeUsage(stdout)
|
||||||
|
return exitOK
|
||||||
|
}
|
||||||
|
fmt.Fprintln(stderr, "Hinweis: `talk --help` zeigt die Verwendung.")
|
||||||
|
return exitUsage
|
||||||
|
}
|
||||||
|
if *showVersion {
|
||||||
|
fmt.Fprintln(stdout, versionString())
|
||||||
|
return exitOK
|
||||||
|
}
|
||||||
|
if *help {
|
||||||
|
writeUsage(stdout)
|
||||||
|
return exitOK
|
||||||
|
}
|
||||||
|
return notImplemented("say", stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// versionString rendert die Versionsausgabe: `talk version <version>` plus
|
||||||
|
// Commit-Hash in Klammern, falls per ldflags injiziert.
|
||||||
|
func versionString() string {
|
||||||
|
if commit == "" {
|
||||||
|
return fmt.Sprintf("talk version %s", version)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("talk version %s (%s)", version, commit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// notImplemented meldet einen noch nicht implementierten Befehl auf stderr
|
||||||
|
// und liefert den dafür vorgesehenen Exit-Code (non-zero).
|
||||||
|
func notImplemented(name string, stderr io.Writer) int {
|
||||||
|
fmt.Fprintf(stderr, "talk %s is not implemented yet\n", name)
|
||||||
|
return exitFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeUsage schreibt die Gesamt-Hilfe; die Befehlsliste wird direkt aus der
|
||||||
|
// Befehlstabelle generiert, damit Hilfe und Dispatch nicht auseinanderlaufen.
|
||||||
|
func writeUsage(w io.Writer) {
|
||||||
|
fmt.Fprint(w, `talk — Sprach-Ausgaben für Skripte und KI-Agenten
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
talk [Flags] "Text" Text ansagen (Abkürzung für: talk say)
|
||||||
|
talk <Befehl> [Flags] Befehl ausführen
|
||||||
|
talk help [Befehl] Hilfe anzeigen
|
||||||
|
|
||||||
|
Befehle:
|
||||||
|
`)
|
||||||
|
for _, cmd := range commands {
|
||||||
|
fmt.Fprintf(w, " %-8s %s\n", cmd.name, cmd.short)
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, `
|
||||||
|
Flags:
|
||||||
|
--version Version anzeigen
|
||||||
|
--help diese Hilfe anzeigen
|
||||||
|
|
||||||
|
Die Flags von say gelten auch für talk "Text"; Details: talk say --help
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeCommandHelp schreibt die Hilfe eines einzelnen Befehls inkl. Flags.
|
||||||
|
func writeCommandHelp(cmd command, w io.Writer) {
|
||||||
|
fmt.Fprintf(w, "Usage:\n talk %s", cmd.name)
|
||||||
|
if cmd.args != "" {
|
||||||
|
fmt.Fprintf(w, " %s", cmd.args)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(w, " [Flags]")
|
||||||
|
fmt.Fprintln(w)
|
||||||
|
if cmd.detail != "" {
|
||||||
|
fmt.Fprintf(w, "%s\n\n", cmd.detail)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(w, "Flags:")
|
||||||
|
fs, _ := newCommandFlagSet(cmd, w)
|
||||||
|
fs.PrintDefaults()
|
||||||
|
fmt.Fprintln(w, "\nStatus: noch nicht implementiert (WIP).")
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// wantSubcommands ist die bindende CLI-Surface aus der Spec — bewusst als
|
||||||
|
// hartkodierte Liste getestet (nicht aus der Befehlstabelle abgeleitet),
|
||||||
|
// damit die Tests anschlagen, wenn Tabelle und Spec auseinanderlaufen.
|
||||||
|
var wantSubcommands = []string{
|
||||||
|
"say", "wrap", "mute", "unmute", "status", "cancel", "voices", "test", "doctor", "setup",
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHelpListetAlleSubcommands(t *testing.T) {
|
||||||
|
var out, errBuf bytes.Buffer
|
||||||
|
code := run([]string{"--help"}, &out, &errBuf)
|
||||||
|
if code != exitOK {
|
||||||
|
t.Fatalf("--help: Exit-Code = %d, want %d (stderr: %q)", code, exitOK, errBuf.String())
|
||||||
|
}
|
||||||
|
lines := strings.Split(out.String(), "\n")
|
||||||
|
for _, name := range wantSubcommands {
|
||||||
|
found := false
|
||||||
|
for _, ln := range lines {
|
||||||
|
if strings.HasPrefix(strings.TrimLeft(ln, " "), name+" ") {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("--help listet Befehl %q nicht (Output: %q)", name, out.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersionDefault(t *testing.T) {
|
||||||
|
var out, errBuf bytes.Buffer
|
||||||
|
code := run([]string{"--version"}, &out, &errBuf)
|
||||||
|
if code != exitOK {
|
||||||
|
t.Fatalf("--version: Exit-Code = %d, want %d", code, exitOK)
|
||||||
|
}
|
||||||
|
if got, want := out.String(), "talk version dev\n"; got != want {
|
||||||
|
t.Errorf("--version Output = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersionStringMitCommit(t *testing.T) {
|
||||||
|
oldCommit := commit
|
||||||
|
commit = "c0ffee1"
|
||||||
|
defer func() { commit = oldCommit }()
|
||||||
|
|
||||||
|
if got, want := versionString(), "talk version dev (c0ffee1)"; got != want {
|
||||||
|
t.Errorf("versionString() = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSubcommandStubs(t *testing.T) {
|
||||||
|
for _, name := range wantSubcommands {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
var out, errBuf bytes.Buffer
|
||||||
|
code := run([]string{name}, &out, &errBuf)
|
||||||
|
if code == exitOK {
|
||||||
|
t.Errorf("talk %s: Exit-Code 0, want non-zero für Stub", name)
|
||||||
|
}
|
||||||
|
if !strings.Contains(errBuf.String(), "not implemented yet") {
|
||||||
|
t.Errorf("talk %s: stderr = %q, will Hinweis auf nicht implementiert", name, errBuf.String())
|
||||||
|
}
|
||||||
|
if !strings.Contains(errBuf.String(), name) {
|
||||||
|
t.Errorf("talk %s: stderr nennt den Befehlsnamen nicht: %q", name, errBuf.String())
|
||||||
|
}
|
||||||
|
if out.Len() != 0 {
|
||||||
|
t.Errorf("talk %s: unerwartete stdout-Ausgabe %q", name, out.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultAufrufMapptAufSay(t *testing.T) {
|
||||||
|
var out, errBuf bytes.Buffer
|
||||||
|
code := run([]string{"Hallo Welt"}, &out, &errBuf)
|
||||||
|
if code != exitFailure {
|
||||||
|
t.Fatalf(`talk "Hallo Welt": Exit-Code = %d, want %d`, code, exitFailure)
|
||||||
|
}
|
||||||
|
if !strings.Contains(errBuf.String(), "talk say is not implemented yet") {
|
||||||
|
t.Errorf("Default-Aufruf mappt offenbar nicht auf say, stderr = %q", errBuf.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSayHelpZeigtFlags(t *testing.T) {
|
||||||
|
var out, errBuf bytes.Buffer
|
||||||
|
code := run([]string{"say", "--help"}, &out, &errBuf)
|
||||||
|
if code != exitOK {
|
||||||
|
t.Fatalf("talk say --help: Exit-Code = %d, want %d", code, exitOK)
|
||||||
|
}
|
||||||
|
for _, f := range []string{"-wait", "-now", "-priority", "-voice", "-lang", "-provider"} {
|
||||||
|
if !strings.Contains(out.String(), f) {
|
||||||
|
t.Errorf("talk say --help zeigt Flag %s nicht (Output: %q)", f, out.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHelpSubcommand(t *testing.T) {
|
||||||
|
t.Run("ohne Argument", func(t *testing.T) {
|
||||||
|
var out, errBuf bytes.Buffer
|
||||||
|
if code := run([]string{"help"}, &out, &errBuf); code != exitOK {
|
||||||
|
t.Fatalf("talk help: Exit-Code = %d, want %d", code, exitOK)
|
||||||
|
}
|
||||||
|
for _, name := range wantSubcommands {
|
||||||
|
if !strings.Contains(out.String(), name) {
|
||||||
|
t.Errorf("talk help listet %q nicht", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("mit Befehl", func(t *testing.T) {
|
||||||
|
var out, errBuf bytes.Buffer
|
||||||
|
if code := run([]string{"help", "mute"}, &out, &errBuf); code != exitOK {
|
||||||
|
t.Fatalf("talk help mute: Exit-Code = %d, want %d", code, exitOK)
|
||||||
|
}
|
||||||
|
if !strings.Contains(out.String(), "talk mute") {
|
||||||
|
t.Errorf("talk help mute zeigt Usage von mute nicht: %q", out.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOhneArgumenteZeigtUsage(t *testing.T) {
|
||||||
|
var out, errBuf bytes.Buffer
|
||||||
|
code := run(nil, &out, &errBuf)
|
||||||
|
if code != exitUsage {
|
||||||
|
t.Fatalf("talk ohne Argumente: Exit-Code = %d, want %d", code, exitUsage)
|
||||||
|
}
|
||||||
|
if !strings.Contains(errBuf.String(), "Usage") {
|
||||||
|
t.Errorf("talk ohne Argumente: Usage fehlt auf stderr: %q", errBuf.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnbekanntesFlag(t *testing.T) {
|
||||||
|
var out, errBuf bytes.Buffer
|
||||||
|
code := run([]string{"--bogus"}, &out, &errBuf)
|
||||||
|
if code != exitUsage {
|
||||||
|
t.Fatalf("talk --bogus: Exit-Code = %d, want %d", code, exitUsage)
|
||||||
|
}
|
||||||
|
if !strings.Contains(errBuf.String(), "bogus") {
|
||||||
|
t.Errorf("talk --bogus: Fehlermeldung fehlt auf stderr: %q", errBuf.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// talk ist die CLI für Sprach-Ausgaben (talk 2.0).
|
||||||
|
//
|
||||||
|
// Dieser Stand (Task 01) liefert das Gerüst: Subcommand-Dispatch, --version
|
||||||
|
// und alle Befehle als Stubs. Die Funktionalität entsteht in den Tasks 02–09.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
// Build-Informationen. Werden beim Release per -ldflags injiziert, z. B.
|
||||||
|
//
|
||||||
|
// go build -ldflags "-X main.version=1.0.0 -X main.commit=abc1234" ./cmd/talk
|
||||||
|
//
|
||||||
|
// Defaults: version "dev", commit leer (siehe auch .goreleaser.yaml, Task 11).
|
||||||
|
var (
|
||||||
|
version = "dev"
|
||||||
|
commit = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
os.Exit(run(os.Args[1:], os.Stdout, os.Stderr))
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// talkd ist der Daemon für talk 2.0 (Queue, Mute-State, HTTP-API, MCP).
|
||||||
|
//
|
||||||
|
// Dieser Stand (Task 01) ist ein Platzhalter: talkd startet, loggt eine Zeile
|
||||||
|
// und beendet sich mit Exit-Code 0. Die eigentliche Daemon-Implementierung
|
||||||
|
// (HTTP-Server auf 127.0.0.1, serialisierte Ansage-Queue, persistentes Mute)
|
||||||
|
// entsteht in Task 04.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "log"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.Println("talkd: Platzhalter — Daemon-Implementierung folgt in Task 04")
|
||||||
|
}
|
||||||
Reference in new issue
Block a user