Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
persist-credentials: false
submodules: recursive
Expand All @@ -66,7 +66,7 @@ jobs:
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.31.0/cargo-dist-installer.sh | sh"
- name: Cache dist
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
with:
name: cargo-dist-cache
path: ~/.cargo/bin/dist
Expand All @@ -82,7 +82,7 @@ jobs:
cat plan-dist-manifest.json
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
with:
name: artifacts-plan-dist-manifest
path: plan-dist-manifest.json
Expand Down Expand Up @@ -116,7 +116,7 @@ jobs:
- name: enable windows longpaths
run: |
git config --global core.longpaths true
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
persist-credentials: false
submodules: recursive
Expand All @@ -131,7 +131,7 @@ jobs:
run: ${{ matrix.install_dist.run }}
# Get the dist-manifest
- name: Fetch local artifacts
uses: actions/download-artifact@v7
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
pattern: artifacts-*
path: target/distrib/
Expand All @@ -158,7 +158,7 @@ jobs:

cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
with:
name: artifacts-build-local-${{ join(matrix.targets, '_') }}
path: |
Expand All @@ -175,19 +175,19 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
persist-credentials: false
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@v7
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
- name: Fetch local artifacts
uses: actions/download-artifact@v7
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
pattern: artifacts-*
path: target/distrib/
Expand All @@ -205,7 +205,7 @@ jobs:

cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
with:
name: artifacts-build-global
path: |
Expand All @@ -225,19 +225,19 @@ jobs:
outputs:
val: ${{ steps.host.outputs.manifest }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
persist-credentials: false
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@v7
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
# Fetch artifacts from scratch-storage
- name: Fetch artifacts
uses: actions/download-artifact@v7
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
pattern: artifacts-*
path: target/distrib/
Expand All @@ -250,14 +250,14 @@ jobs:
cat dist-manifest.json
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
with:
# Overwrite the previous copy
name: artifacts-dist-manifest
path: dist-manifest.json
# Create a GitHub Release while uploading all files to it
- name: "Download GitHub Artifacts"
uses: actions/download-artifact@v7
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
pattern: artifacts-*
path: artifacts
Expand Down Expand Up @@ -290,14 +290,14 @@ jobs:
GITHUB_EMAIL: "admin+bot@axo.dev"
if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
persist-credentials: true
repository: "64bit/homebrew-tap"
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
# So we have access to the formula
- name: Fetch homebrew formulae
uses: actions/download-artifact@v7
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
pattern: artifacts-*
path: Formula/
Expand Down Expand Up @@ -337,7 +337,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
persist-credentials: false
submodules: recursive
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,18 @@

**commandOK** is a Spotlight-like command generator for your terminal. Pops up when you need it and gets out of the way when you don't.

Built with [Ratatui](https://ratatui.rs) and powered by your choice of LLM provider.
Built with [Ratatui](https://ratatui.rs) and powered by your choice of public, private or local LLM provider.

**WARN**: you must always verify the generated command before accepting it

## Install

```bash
brew install 64bit/tap/commandok
```

OR

```bash
cargo install commandok
```
Expand Down
8 changes: 8 additions & 0 deletions dist-workspace.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,11 @@ tap = "64bit/homebrew-tap"
targets = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"]
# Publish jobs to run in CI
publish-jobs = ["homebrew"]


# https://axodotdev.github.io/cargo-dist/book/ci/customizing.html#pinned-actions-commits
[dist.github-action-commits]
"actions/checkout" = "de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6.0.2
"actions/upload-artifact" = "bbbca2ddaa5d8feaa63e36b76fdaad77386f024f" # v7.0.0
"actions/download-artifact" = "3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c" # v8.0.1
"actions/attest-build-provenance" = "00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8" # v3.1.0
52 changes: 50 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub struct Config {
pub ollama: Option<ProviderConfig>,
pub openrouter: Option<ProviderConfig>,
pub xai: Option<ProviderConfig>,
pub litert_lm: Option<ProviderConfig>,
}

#[derive(Deserialize)]
Expand All @@ -29,6 +30,8 @@ pub struct ProviderConfig {
pub model: String,
#[serde(default)]
pub api_url: String,
#[serde(default)]
pub huggingface_repo: String,
}

fn config_dir() -> PathBuf {
Expand All @@ -41,8 +44,14 @@ fn config_path() -> PathBuf {
}

const DEFAULT_CONFIG: &str = r#"[commandok]
provider = "anthropic" # Options: anthropic, openai, google, mistral, ollama, openrouter, xai
system_prompt = "You are a terminal command generator. Given a natural language description, output ONLY the shell command appropriate for the user's OS and shell. No explanation, no markdown, no code blocks, no backticks. Just the raw command."
# Options: anthropic, openai, google, mistral, ollama,
# openrouter, xai, litert_lm
provider = "anthropic"
system_prompt = """\
You are a terminal command generator. Given a natural language description, output ONLY \
the shell command appropriate for the user's OS and shell. No explanation, no markdown, no code blocks, \
no backticks. Just the raw command.\
"""

[anthropic]
api_key = ""
Expand Down Expand Up @@ -74,6 +83,10 @@ model = "qwen/qwen3.6-plus:free"
api_key = ""
model = "grok-4.20-0309-reasoning"
# api_url = "https://api.x.ai/v1" # default

[litert_lm]
model = "gemma-4-E2B-it.litertlm"
huggingface_repo = "litert-community/gemma-4-E2B-it-litert-lm"
"#;

pub fn load() -> Result<Config, String> {
Expand All @@ -85,6 +98,12 @@ pub fn load() -> Result<Config, String> {
fs::File::create(&path)
.and_then(|mut f| f.write_all(DEFAULT_CONFIG.as_bytes()))
.map_err(|e| format!("Failed to write {}: {e}", path.display()))?;
} else if let Some(migrated) = migrate_config(
&fs::read_to_string(&path)
.map_err(|e| format!("Failed to read {}: {e}", path.display()))?,
) {
fs::write(&path, &migrated)
.map_err(|e| format!("Failed to update {}: {e}", path.display()))?;
}

let content =
Expand All @@ -96,6 +115,33 @@ pub fn load() -> Result<Config, String> {
Ok(config)
}

/// Compares the user's existing config against DEFAULT_CONFIG and returns an
/// updated string with any missing provider sections appended at the end.
/// Returns `None` when nothing is missing (no write needed).
fn migrate_config(existing: &str) -> Option<String> {
let existing_table: toml::Table = toml::from_str(existing).ok()?;
let default_table: toml::Table = toml::from_str(DEFAULT_CONFIG).ok()?;

let mut missing = toml::Table::new();
for (key, value) in &default_table {
if !existing_table.contains_key(key) {
missing.insert(key.clone(), value.clone());
}
}

if missing.is_empty() {
return None;
}

let mut result = existing.to_string();
if !result.ends_with('\n') {
result.push('\n');
}
result.push('\n');
result.push_str(&toml::to_string_pretty(&missing).ok()?);
Some(result)
}

const PROVIDER_ORDER: &[&str] = &[
"anthropic",
"openai",
Expand All @@ -104,6 +150,7 @@ const PROVIDER_ORDER: &[&str] = &[
"ollama",
"openrouter",
"xai",
"litert_lm",
];

impl Config {
Expand All @@ -116,6 +163,7 @@ impl Config {
"ollama" => self.ollama.as_ref(),
"openrouter" => self.openrouter.as_ref(),
"xai" => self.xai.as_ref(),
"litert_lm" => self.litert_lm.as_ref(),
_ => None,
}
}
Expand Down
Loading