cmdo-cli 0.1.0__tar.gz

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.
cmdo_cli-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yijiang
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.4
2
+ Name: cmdo-cli
3
+ Version: 0.1.0
4
+ Summary: Natural language to shell commands — just say what you want and cmdo does it.
5
+ Author: Yijiang Pang
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/YijiangPang/cmdo
8
+ Project-URL: Repository, https://github.com/YijiangPang/cmdo
9
+ Project-URL: Issues, https://github.com/YijiangPang/cmdo/issues
10
+ Keywords: cli,terminal,ai,shell,natural-language,command-line,openai,gpt
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: Operating System :: MacOS
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: System :: Shells
23
+ Classifier: Topic :: Utilities
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: click>=8.0
28
+ Requires-Dist: rich>=13.0
29
+ Requires-Dist: openai>=1.0
30
+ Requires-Dist: tomli>=2.0; python_version < "3.11"
31
+ Requires-Dist: tomli-w>=1.0
32
+ Requires-Dist: pyperclip>=1.8
33
+ Provides-Extra: dev
34
+ Requires-Dist: pytest>=7.0; extra == "dev"
35
+ Requires-Dist: pytest-mock>=3.0; extra == "dev"
36
+ Requires-Dist: build; extra == "dev"
37
+ Requires-Dist: twine; extra == "dev"
38
+ Dynamic: license-file
39
+
40
+ # cmdo
41
+
42
+ **Natural language to shell commands.** Just say what you want and cmdo does it.
43
+
44
+ ```
45
+ $ cmdo "compress the checkpoints folder"
46
+
47
+ 🤖 Will run:
48
+ ╭──────────────────────────────────────────────╮
49
+ │ tar -czf checkpoints.tar.gz checkpoints/ │
50
+ ╰──────────────────────────────────────────────╯
51
+ 📝 Compresses the "checkpoints" directory into a gzipped tarball.
52
+
53
+ [Y] Execute [e] Edit [c] Copy [n] Cancel
54
+ ```
55
+
56
+ ## Features
57
+
58
+ - **Translates natural language** into accurate shell commands using GPT
59
+ - **Safety-first**: commands are classified as Safe / Caution / Dangerous with color-coded warnings
60
+ - **Hard-blocks** catastrophic commands (fork bombs, `rm -rf /`, disk wipes)
61
+ - **Context-aware**: knows your OS, shell, current directory, installed tools, and git branch
62
+ - **Works everywhere**: any terminal — iTerm, VSCode, Terminal.app, SSH, tmux
63
+
64
+ ## Install
65
+
66
+ ```bash
67
+ pip install cmdo
68
+ ```
69
+
70
+ Requires Python 3.10+.
71
+
72
+ ## Quick Start
73
+
74
+ ```bash
75
+ # First-time setup — enter your OpenAI API key
76
+ cmdo --config
77
+
78
+ # Use it
79
+ cmdo "find all Python files larger than 1MB"
80
+ cmdo "start a local HTTP server on port 8080"
81
+ cmdo "show disk usage sorted by size"
82
+
83
+ # Generate without executing
84
+ cmdo --dry-run "delete the temp folder"
85
+
86
+ # Auto-confirm safe commands
87
+ cmdo --yes "list all docker containers"
88
+ ```
89
+
90
+ ## Safety
91
+
92
+ Every generated command goes through two layers of safety checking:
93
+
94
+ 1. **LLM classification** — the model labels each command as SAFE, CAUTION, or DANGEROUS
95
+ 2. **Local pattern matching** — a regex-based classifier catches anything the LLM misses
96
+
97
+ | Risk Level | Confirmation | Auto-confirm (`--yes`) |
98
+ |---|---|---|
99
+ | **SAFE** (green) | Single keypress `Y` | Allowed |
100
+ | **CAUTION** (yellow) | Single keypress `Y` | Allowed |
101
+ | **DANGEROUS** (red) | Type full word `yes` | Never |
102
+
103
+ Certain commands are **hard-blocked** and can never be executed:
104
+ - Fork bombs
105
+ - Full disk wipes (`dd if=/dev/zero of=/dev/sda`)
106
+ - System-wide deletion (`rm -rf /`)
107
+
108
+ ## Configuration
109
+
110
+ ```bash
111
+ cmdo --config # Interactive setup wizard
112
+ cmdo --config --show # View current config (API key masked)
113
+ cmdo --config --reset # Reset to defaults
114
+ ```
115
+
116
+ Config is stored in `~/.config/cmdo/config.toml`.
117
+
118
+ ## CLI Reference
119
+
120
+ ```
121
+ Usage: cmdo [OPTIONS] [QUERY]...
122
+
123
+ Options:
124
+ --config Configure cmdo
125
+ --show Show current configuration (use with --config)
126
+ --reset Reset configuration (use with --config)
127
+ -d, --dry-run Generate command without executing
128
+ -y, --yes Auto-confirm safe commands
129
+ -m, --model TEXT Override default model for one query
130
+ -V, --version Show version
131
+ --help Show help
132
+ ```
133
+
134
+ ## License
135
+
136
+ MIT
@@ -0,0 +1,97 @@
1
+ # cmdo
2
+
3
+ **Natural language to shell commands.** Just say what you want and cmdo does it.
4
+
5
+ ```
6
+ $ cmdo "compress the checkpoints folder"
7
+
8
+ 🤖 Will run:
9
+ ╭──────────────────────────────────────────────╮
10
+ │ tar -czf checkpoints.tar.gz checkpoints/ │
11
+ ╰──────────────────────────────────────────────╯
12
+ 📝 Compresses the "checkpoints" directory into a gzipped tarball.
13
+
14
+ [Y] Execute [e] Edit [c] Copy [n] Cancel
15
+ ```
16
+
17
+ ## Features
18
+
19
+ - **Translates natural language** into accurate shell commands using GPT
20
+ - **Safety-first**: commands are classified as Safe / Caution / Dangerous with color-coded warnings
21
+ - **Hard-blocks** catastrophic commands (fork bombs, `rm -rf /`, disk wipes)
22
+ - **Context-aware**: knows your OS, shell, current directory, installed tools, and git branch
23
+ - **Works everywhere**: any terminal — iTerm, VSCode, Terminal.app, SSH, tmux
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ pip install cmdo
29
+ ```
30
+
31
+ Requires Python 3.10+.
32
+
33
+ ## Quick Start
34
+
35
+ ```bash
36
+ # First-time setup — enter your OpenAI API key
37
+ cmdo --config
38
+
39
+ # Use it
40
+ cmdo "find all Python files larger than 1MB"
41
+ cmdo "start a local HTTP server on port 8080"
42
+ cmdo "show disk usage sorted by size"
43
+
44
+ # Generate without executing
45
+ cmdo --dry-run "delete the temp folder"
46
+
47
+ # Auto-confirm safe commands
48
+ cmdo --yes "list all docker containers"
49
+ ```
50
+
51
+ ## Safety
52
+
53
+ Every generated command goes through two layers of safety checking:
54
+
55
+ 1. **LLM classification** — the model labels each command as SAFE, CAUTION, or DANGEROUS
56
+ 2. **Local pattern matching** — a regex-based classifier catches anything the LLM misses
57
+
58
+ | Risk Level | Confirmation | Auto-confirm (`--yes`) |
59
+ |---|---|---|
60
+ | **SAFE** (green) | Single keypress `Y` | Allowed |
61
+ | **CAUTION** (yellow) | Single keypress `Y` | Allowed |
62
+ | **DANGEROUS** (red) | Type full word `yes` | Never |
63
+
64
+ Certain commands are **hard-blocked** and can never be executed:
65
+ - Fork bombs
66
+ - Full disk wipes (`dd if=/dev/zero of=/dev/sda`)
67
+ - System-wide deletion (`rm -rf /`)
68
+
69
+ ## Configuration
70
+
71
+ ```bash
72
+ cmdo --config # Interactive setup wizard
73
+ cmdo --config --show # View current config (API key masked)
74
+ cmdo --config --reset # Reset to defaults
75
+ ```
76
+
77
+ Config is stored in `~/.config/cmdo/config.toml`.
78
+
79
+ ## CLI Reference
80
+
81
+ ```
82
+ Usage: cmdo [OPTIONS] [QUERY]...
83
+
84
+ Options:
85
+ --config Configure cmdo
86
+ --show Show current configuration (use with --config)
87
+ --reset Reset configuration (use with --config)
88
+ -d, --dry-run Generate command without executing
89
+ -y, --yes Auto-confirm safe commands
90
+ -m, --model TEXT Override default model for one query
91
+ -V, --version Show version
92
+ --help Show help
93
+ ```
94
+
95
+ ## License
96
+
97
+ MIT
@@ -0,0 +1,57 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "cmdo-cli"
7
+ version = "0.1.0"
8
+ description = "Natural language to shell commands — just say what you want and cmdo does it."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ {name = "Yijiang Pang"},
14
+ ]
15
+ keywords = ["cli", "terminal", "ai", "shell", "natural-language", "command-line", "openai", "gpt"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Environment :: Console",
19
+ "Intended Audience :: Developers",
20
+ "Intended Audience :: System Administrators",
21
+ "Operating System :: MacOS",
22
+ "Operating System :: POSIX :: Linux",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
28
+ "Topic :: System :: Shells",
29
+ "Topic :: Utilities",
30
+ ]
31
+ dependencies = [
32
+ "click>=8.0",
33
+ "rich>=13.0",
34
+ "openai>=1.0",
35
+ "tomli>=2.0; python_version < '3.11'",
36
+ "tomli-w>=1.0",
37
+ "pyperclip>=1.8",
38
+ ]
39
+
40
+ [project.optional-dependencies]
41
+ dev = [
42
+ "pytest>=7.0",
43
+ "pytest-mock>=3.0",
44
+ "build",
45
+ "twine",
46
+ ]
47
+
48
+ [project.urls]
49
+ Homepage = "https://github.com/YijiangPang/cmdo"
50
+ Repository = "https://github.com/YijiangPang/cmdo"
51
+ Issues = "https://github.com/YijiangPang/cmdo/issues"
52
+
53
+ [project.scripts]
54
+ cmdo = "cmdo.cli:main"
55
+
56
+ [tool.setuptools.packages.find]
57
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """cmdo — Natural language to shell commands."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,174 @@
1
+ """CLI entry point for cmdo."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+
7
+ import click
8
+ from rich.console import Console
9
+
10
+ from cmdo import __version__
11
+
12
+ console = Console()
13
+
14
+
15
+ @click.command(context_settings={"ignore_unknown_options": True})
16
+ @click.argument("query", nargs=-1)
17
+ @click.option("--config", "do_config", is_flag=True, help="Configure cmdo")
18
+ @click.option("--show", is_flag=True, help="Show current configuration (use with --config)")
19
+ @click.option("--reset", is_flag=True, help="Reset configuration (use with --config)")
20
+ @click.option("--dry-run", "-d", is_flag=True, help="Generate command without executing")
21
+ @click.option("--yes", "-y", is_flag=True, help="Auto-confirm safe commands")
22
+ @click.option("--model", "-m", default=None, help="Override default model")
23
+ @click.option("--version", "-V", is_flag=True, help="Show version")
24
+ def main(
25
+ query: tuple[str, ...],
26
+ do_config: bool,
27
+ show: bool,
28
+ reset: bool,
29
+ dry_run: bool,
30
+ yes: bool,
31
+ model: str | None,
32
+ version: bool,
33
+ ) -> None:
34
+ """cmdo — Natural language to shell commands.
35
+
36
+ \b
37
+ Examples:
38
+ cmdo "find all Python files larger than 1MB"
39
+ cmdo "start a local HTTP server on port 8080"
40
+ cmdo --explain "tar -czf archive.tar.gz dir/"
41
+ cmdo --dry-run "delete the temp folder"
42
+ """
43
+ if version:
44
+ click.echo(f"cmdo v{__version__}")
45
+ return
46
+
47
+ # Config management
48
+ if do_config:
49
+ if show:
50
+ from cmdo.config import show_config
51
+ show_config()
52
+ elif reset:
53
+ from cmdo.config import reset_config
54
+ reset_config()
55
+ else:
56
+ from cmdo.config import configure
57
+ configure()
58
+ return
59
+
60
+ # No query provided
61
+ if not query:
62
+ click.echo(f"cmdo v{__version__} — Natural language to shell commands")
63
+ click.echo('Usage: cmdo "your instruction"')
64
+ click.echo(" cmdo --config Set up or reconfigure")
65
+ click.echo(" cmdo --help Show full help")
66
+ return
67
+
68
+ # Core flow
69
+ query_str = " ".join(query)
70
+ _run_query(query_str, dry_run=dry_run, auto_yes=yes, model_override=model)
71
+
72
+
73
+ def _run_query(
74
+ query: str,
75
+ *,
76
+ dry_run: bool = False,
77
+ auto_yes: bool = False,
78
+ model_override: str | None = None,
79
+ ) -> None:
80
+ """Core flow: query → generate → display → confirm → execute."""
81
+ from cmdo.clipboard import copy_to_clipboard
82
+ from cmdo.config import ensure_configured
83
+ from cmdo.context import gather_context
84
+ from cmdo.display import (
85
+ display_command,
86
+ display_error,
87
+ display_execution_result,
88
+ display_forbidden,
89
+ edit_command,
90
+ prompt_user,
91
+ )
92
+ from cmdo.executor import execute_command
93
+ from cmdo.llm.client import generate_command
94
+ from cmdo.models import RiskLevel, UserAction
95
+ from cmdo.safety.classifier import classify_risk, upgrade_risk
96
+ from cmdo.safety.forbidden import check_forbidden
97
+
98
+ # 1. Ensure configured
99
+ config = ensure_configured()
100
+ if model_override:
101
+ config.model = model_override
102
+
103
+ # 2. Gather context
104
+ with console.status("[dim]Gathering context...[/dim]", spinner="dots"):
105
+ context = gather_context()
106
+
107
+ # 3. Generate command via LLM
108
+ try:
109
+ with console.status("[dim]Thinking...[/dim]", spinner="dots"):
110
+ result = generate_command(query, context, config)
111
+ except Exception as e:
112
+ display_error(f"Failed to generate command: {e}")
113
+ sys.exit(2)
114
+
115
+ if not result.command:
116
+ display_error("Could not generate a command for that request.")
117
+ sys.exit(1)
118
+
119
+ # 4. Safety checks
120
+ forbidden_msg = check_forbidden(result.command)
121
+ if forbidden_msg:
122
+ display_forbidden(forbidden_msg)
123
+ sys.exit(1)
124
+
125
+ local_risk, local_reason = classify_risk(result.command)
126
+ result.risk_level = upgrade_risk(result.risk_level, local_risk)
127
+ if local_reason and result.risk_reason is None:
128
+ result.risk_reason = local_reason
129
+
130
+ # 5. Display
131
+ display_command(result)
132
+
133
+ # 6. Dry run — stop here
134
+ if dry_run:
135
+ return
136
+
137
+ # 7. Auto-confirm if --yes flag or config auto_confirm_safe
138
+ if (auto_yes or config.auto_confirm_safe) and result.risk_level != RiskLevel.DANGEROUS:
139
+ action = UserAction.EXECUTE
140
+ else:
141
+ action = prompt_user(result)
142
+
143
+ # 8. Handle action
144
+ if action == UserAction.CANCEL:
145
+ console.print("[dim]Cancelled.[/dim]")
146
+ sys.exit(1)
147
+
148
+ if action == UserAction.COPY:
149
+ if copy_to_clipboard(result.command):
150
+ console.print("[green]📋 Copied to clipboard![/green]")
151
+ else:
152
+ console.print(f"[yellow]Could not copy. Here's the command:[/yellow]\n{result.command}")
153
+ return
154
+
155
+ if action == UserAction.EDIT:
156
+ edited = edit_command(result.command)
157
+ if edited != result.command:
158
+ # Re-check safety on edited command
159
+ forbidden_msg = check_forbidden(edited)
160
+ if forbidden_msg:
161
+ display_forbidden(forbidden_msg)
162
+ sys.exit(1)
163
+ result.command = edited
164
+
165
+ # 9. Execute
166
+ stepwise = action == UserAction.EXECUTE_STEPWISE
167
+ exec_result = execute_command(result.command, stepwise=stepwise)
168
+ display_execution_result(exec_result.exit_code, exec_result.duration)
169
+
170
+ sys.exit(0 if exec_result.exit_code == 0 else 2)
171
+
172
+
173
+ if __name__ == "__main__":
174
+ main()
@@ -0,0 +1,14 @@
1
+ """Clipboard integration."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ def copy_to_clipboard(command: str) -> bool:
7
+ """Copy command to system clipboard. Returns True on success."""
8
+ try:
9
+ import pyperclip
10
+
11
+ pyperclip.copy(command)
12
+ return True
13
+ except Exception:
14
+ return False