summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-08-17 08:59:53 +0300
committerPaul Buetow <paul@buetow.org>2025-08-17 08:59:53 +0300
commitf50a5bd440619e99642e5f26a193ab812d7de9b3 (patch)
treee9bf9a40d9fb5d1c175a5d56f3840e3dbd518c62
parent6a5b04210c4610375bc54081d66ea213db781675 (diff)
docs(cli): document hexai CLI usage and behaviors
- Inputs from stdin/arg, concatenation rules - Stderr styling, immediate provider/model, final stats line - Default concise style; 'explain' keyword for verbose answers - Examples and exit codes
-rw-r--r--README.md34
-rw-r--r--cmd/hexai/main.go21
2 files changed, 49 insertions, 6 deletions
diff --git a/README.md b/README.md
index 837292c..f7f919b 100644
--- a/README.md
+++ b/README.md
@@ -57,6 +57,40 @@ Notes:
Notes for `hexai` (CLI):
- Prints LLM output to stdout.
- Prints provider/model immediately to stderr, and a summary to stderr at the end (time, input bytes, output bytes, provider/model).
+- Default response style: short answers. If the prompt asks for commands, outputs only the commands with no explanation. Include the word `explain` anywhere in the prompt to request a verbose explanation.
+
+### Hexai CLI behavior
+
+- Inputs: reads from stdin, from a single argument, or both.
+ - If both are provided, Hexai concatenates them with a blank line in between.
+- Output routing:
+ - Stdout: the LLM response only (no decorations).
+ - Stderr: metadata and progress in grey on black (styled via ANSI):
+ - Provider/model printed immediately when the request starts.
+ - A final stats line on a new line: `done provider=… model=… time=… in_bytes=… out_bytes=…`.
+- Default style: concise answers.
+ - If the prompt asks for commands, outputs only the commands with no commentary.
+ - Add the word `explain` in your prompt to request a verbose explanation.
+- Exit codes: `0` success, `1` provider/config error, `2` no input.
+
+Examples:
+
+```
+# From stdin only
+cat SOMEFILE.txt | hexai
+
+# From arg only
+hexai 'summarize: list 3 bullets'
+
+# From both (stdin first, then arg)
+cat SOMEFILE.txt | hexai 'explain the tradeoffs'
+
+# Commands-only output (no explanation)
+hexai 'install ripgrep on macOS'
+
+# Verbose explanation
+hexai 'install ripgrep on macOS and explain'
+```
Notes:
- Token estimation for truncation uses a simple 4 chars/token heuristic.
diff --git a/cmd/hexai/main.go b/cmd/hexai/main.go
index 88fa69b..c356800 100644
--- a/cmd/hexai/main.go
+++ b/cmd/hexai/main.go
@@ -13,6 +13,7 @@ import (
"hexai/internal"
"hexai/internal/appconfig"
"hexai/internal/llm"
+ "hexai/internal/logging"
)
func main() {
@@ -43,7 +44,7 @@ func main() {
case argData != "":
input = argData
default:
- fmt.Fprintln(os.Stderr, "hexai: no input provided; pass text as an argument or via stdin")
+ fmt.Fprintln(os.Stderr, logging.AnsiBase+"hexai: no input provided; pass text as an argument or via stdin"+logging.AnsiReset)
os.Exit(2)
}
@@ -64,20 +65,28 @@ func main() {
cpKey := os.Getenv("COPILOT_API_KEY")
client, err := llm.NewFromConfig(llmCfg, oaKey, cpKey)
if err != nil {
- fmt.Fprintf(os.Stderr, "hexai: LLM disabled: %v\n", err)
+ fmt.Fprintf(os.Stderr, logging.AnsiBase+"hexai: LLM disabled: %v"+logging.AnsiReset+"\n", err)
os.Exit(1)
}
// Print provider/model immediately to stderr
- fmt.Fprintf(os.Stderr, "provider=%s model=%s\n", client.Name(), client.DefaultModel())
+ fmt.Fprintf(os.Stderr, logging.AnsiBase+"provider=%s model=%s"+logging.AnsiReset+"\n", client.Name(), client.DefaultModel())
// Prepare and send request
start := time.Now()
- msgs := []llm.Message{{Role: "user", Content: input}}
+ lower := strings.ToLower(input)
+ system := "You are Hexai CLI. Default to very short, concise answers. If the user asks for commands, output only the commands (one per line) with no commentary or explanation. Only when the word 'explain' appears in the prompt, produce a verbose explanation."
+ if strings.Contains(lower, "explain") {
+ system = "You are Hexai CLI. The user requested an explanation. Provide a clear, verbose explanation with reasoning and details. If commands are needed, include them with brief context."
+ }
+ msgs := []llm.Message{
+ {Role: "system", Content: system},
+ {Role: "user", Content: input},
+ }
out, err := client.Chat(context.Background(), msgs)
dur := time.Since(start)
if err != nil {
- fmt.Fprintf(os.Stderr, "hexai: error: %v\n", err)
+ fmt.Fprintf(os.Stderr, logging.AnsiBase+"hexai: error: %v"+logging.AnsiReset+"\n", err)
os.Exit(1)
}
@@ -87,5 +96,5 @@ func main() {
// Summary to stderr (preceded by a blank line)
inSize := len(input)
outSize := len(out)
- fmt.Fprintf(os.Stderr, "\ndone provider=%s model=%s time=%s in_bytes=%d out_bytes=%d\n", client.Name(), client.DefaultModel(), dur.Round(time.Millisecond), inSize, outSize)
+ fmt.Fprintf(os.Stderr, "\n"+logging.AnsiBase+"done provider=%s model=%s time=%s in_bytes=%d out_bytes=%d"+logging.AnsiReset+"\n", client.Name(), client.DefaultModel(), dur.Round(time.Millisecond), inSize, outSize)
}