Quickstart

This commit is contained in:
K. Hodges 2026-06-06 13:32:00 -07:00
parent c552a93a9a
commit 40216fb635
5 changed files with 292 additions and 12 deletions

View File

@ -6,6 +6,8 @@ A cognitive shell for engineers who still want the controls.
Exoshell is a local-first, shell-adjacent assistant for practitioners who want AI help without giving up operational awareness or control. The shell remains primary; Exoshell suggests, explains, preserves context, and keeps the human in the loop. Exoshell is not designed for “vibe coding.” Exoshell is a local-first, shell-adjacent assistant for practitioners who want AI help without giving up operational awareness or control. The shell remains primary; Exoshell suggests, explains, preserves context, and keeps the human in the loop. Exoshell is not designed for “vibe coding.”
Start here: [Quickstart and Tour](docs/quickstart.md).
For the full project philosophy and design direction, read [docs/DESIGN.md](docs/DESIGN.md). For the full project philosophy and design direction, read [docs/DESIGN.md](docs/DESIGN.md).
## Philosophy ## Philosophy

268
docs/quickstart.md Normal file
View File

@ -0,0 +1,268 @@
# Quickstart
This guide gets Exoshell running and shows the main Phase 2 workflow.
Exoshell suggests commands and explains system work. It does not execute commands.
## Prerequisites
Install a current Rust toolchain with `rustup`.
```sh
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o /tmp/rustup-init.sh
sh /tmp/rustup-init.sh -y --profile default --default-toolchain stable
. "$HOME/.cargo/env"
```
Check the toolchain:
```sh
cargo --version
rustc --version
rustfmt --version
```
On Ubuntu or WSL, provider dependencies may also need:
```sh
sudo apt update
sudo apt install pkg-config libssl-dev
```
## Configure a Provider
For OpenAI-compatible hosted providers, set an API key:
```sh
export OPENAI_API_KEY="..."
```
For a local OpenAI-compatible provider, create a config file:
```toml
[provider]
base_url = "http://localhost:11434/v1"
model = "local-model"
request_timeout_seconds = 120
[shell]
family = "posix"
[interaction]
stance = "operator"
```
Local provider URLs such as `localhost` and `127.0.0.1` do not require an API key.
## Start Exoshell
Run with defaults:
```sh
cargo run
```
Run with a config file:
```sh
cargo run -- --config path/to/config.toml
```
Select a shell family:
```sh
cargo run -- --shell posix
cargo run -- --shell powershell
```
Select an operating stance:
```sh
cargo run -- --stance audit
```
## First Session
At the prompt:
```text
exo> /help
```
Attach a note as explicit context:
```text
exo> /add-note this repo is a Rust CLI called Exoshell
```
Inspect context:
```text
exo> /context
exo> /context stats
```
Ask a question:
```text
exo> What should I inspect before changing command parsing?
```
Exoshell may return suggested commands in fenced shell blocks. Suggested commands are reviewable text. You decide whether to copy and run them in your shell.
## Context Tour
Context is explicit and session-scoped. Exoshell only sends enabled context to the model.
Add a file:
```text
exo> /add-file Cargo.toml
```
Add a shallow directory summary:
```text
exo> /add-dir src
```
Paste command output without Exoshell running the command:
```text
exo> /add-output
paste command output; finish with a single '.' line
... test result: ok
... .
```
Inspect one entry:
```text
exo> /context show ctx-001
```
Control inclusion:
```text
exo> /context disable ctx-001
exo> /context enable ctx-001
```
Control pruning preference:
```text
exo> /context pin ctx-001
exo> /context priority ctx-001 high
```
Remove an entry:
```text
exo> /context remove ctx-001
```
## Stance Tour
Stances change the compact behavior fragment in the prompt.
Show the current stance:
```text
exo> /stance
```
Switch stance:
```text
exo> /stance operator
exo> /stance audit
exo> /stance teach
exo> /stance quiet
```
Use `operator` for concise next steps, `audit` for risk review, `teach` for fuller explanations, and `quiet` for minimal prose.
## Command Suggestion Tour
When a model response includes shell fenced blocks, Exoshell assigns command IDs such as `cmd-001`.
Print a suggested command:
```text
exo> /copy cmd-001
```
Clipboard support is not implemented yet, so `/copy` prints the command. It does not execute it.
Explain a suggestion:
```text
exo> /explain cmd-001
```
Discard a suggestion:
```text
exo> /discard cmd-001
```
Risk warnings are heuristic. Treat a warning as a prompt for careful review. Lack of a warning does not prove a command is safe.
## Session Panel
Show the current operating state:
```text
exo> /panel
```
The panel includes stance, shell family, provider/model, transcript state, context entries, and prompt estimates.
## Multi-Line Prompts
Use `/multi` for longer prompts:
```text
exo> /multi
multi-line input; finish with a single '.' line
... Review this plan:
... 1. Add parser tests.
... 2. Refactor command rendering.
... .
```
## Piped Input
Pipe text into Exoshell as explicit context:
```sh
printf 'build failed in openssl-sys\n' | cargo run
```
Exoshell records piped content as user-provided context. It does not claim to know the upstream command unless you provide that separately.
## Quality Checks
Run:
```sh
cargo fmt --check
cargo test
cargo clippy --all-targets --all-features
```
If `cargo test` fails on Ubuntu or WSL with an OpenSSL or `pkg-config` error, install:
```sh
sudo apt install pkg-config libssl-dev
```
## Exit
Quit the REPL:
```text
exo> /exit
```
If transcripts are enabled, Exoshell writes a markdown transcript at shutdown.

View File

@ -368,7 +368,8 @@ impl App {
if let Some(warning) = suggestion.detected_risk.warning() { if let Some(warning) = suggestion.detected_risk.warning() {
explanation.push_str(&format!("{warning}\n")); explanation.push_str(&format!("{warning}\n"));
} else { } else {
explanation.push_str("No obvious destructive pattern was detected. Review before running.\n"); explanation
.push_str("No obvious destructive pattern was detected. Review before running.\n");
} }
self.transcript self.transcript
.record_command_action(id, "explain", "operator requested explanation"); .record_command_action(id, "explain", "operator requested explanation");
@ -577,10 +578,11 @@ impl CliOptions {
let value = args.next().ok_or_else(|| { let value = args.next().ok_or_else(|| {
crate::config::ConfigError::Invalid("--stance requires a value".into()) crate::config::ConfigError::Invalid("--stance requires a value".into())
})?; })?;
options.stance = options.stance = Some(value.parse().map_err(
Some(value.parse().map_err(|error: crate::prompts::StanceError| { |error: crate::prompts::StanceError| {
crate::config::ConfigError::Invalid(error.to_string()) crate::config::ConfigError::Invalid(error.to_string())
})?); },
)?);
} }
"--no-transcript" => options.transcript_enabled = Some(false), "--no-transcript" => options.transcript_enabled = Some(false),
"--transcript-dir" => { "--transcript-dir" => {

View File

@ -63,10 +63,7 @@ impl CommandRisk {
if self.reasons.is_empty() { if self.reasons.is_empty() {
None None
} else { } else {
Some(format!( Some(format!("review required: {}", self.reasons.join("; ")))
"review required: {}",
self.reasons.join("; ")
))
} }
} }
} }
@ -132,7 +129,9 @@ pub fn detect_command_risk(command: &str, shell: CommandShell) -> CommandRisk {
if lowered.contains("rm -rf") if lowered.contains("rm -rf")
|| lowered.contains("rm -fr") || lowered.contains("rm -fr")
|| lowered.contains("remove-item") && lowered.contains("-recurse") && lowered.contains("-force") || lowered.contains("remove-item")
&& lowered.contains("-recurse")
&& lowered.contains("-force")
|| lowered.contains("del /s") || lowered.contains("del /s")
{ {
reasons.push("recursive or forced deletion".into()); reasons.push("recursive or forced deletion".into());
@ -200,7 +199,11 @@ pub fn render_suggestions(suggestions: &[CommandSuggestion]) -> String {
"- {} [{}]{}", "- {} [{}]{}",
suggestion.id, suggestion.id,
suggestion.shell, suggestion.shell,
if suggestion.discarded { " discarded" } else { "" } if suggestion.discarded {
" discarded"
} else {
""
}
)); ));
if let Some(risk) = suggestion.model_risk { if let Some(risk) = suggestion.model_risk {
rendered.push_str(&format!(" model_risk={risk}")); rendered.push_str(&format!(" model_risk={risk}"));
@ -210,7 +213,9 @@ pub fn render_suggestions(suggestions: &[CommandSuggestion]) -> String {
} }
rendered.push('\n'); rendered.push('\n');
} }
rendered.push_str("Use /copy <id>, /explain <id>, or /discard <id>. Exoshell does not execute commands."); rendered.push_str(
"Use /copy <id>, /explain <id>, or /discard <id>. Exoshell does not execute commands.",
);
rendered rendered
} }

View File

@ -236,7 +236,10 @@ enum TranscriptEntry {
note: String, note: String,
}, },
BudgetWarning(String), BudgetWarning(String),
StanceChange { previous: String, current: String }, StanceChange {
previous: String,
current: String,
},
CommandSuggestion { CommandSuggestion {
id: String, id: String,
shell: String, shell: String,