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 +21 -0
- cmdo_cli-0.1.0/PKG-INFO +136 -0
- cmdo_cli-0.1.0/README.md +97 -0
- cmdo_cli-0.1.0/pyproject.toml +57 -0
- cmdo_cli-0.1.0/setup.cfg +4 -0
- cmdo_cli-0.1.0/src/cmdo/__init__.py +3 -0
- cmdo_cli-0.1.0/src/cmdo/cli.py +174 -0
- cmdo_cli-0.1.0/src/cmdo/clipboard.py +14 -0
- cmdo_cli-0.1.0/src/cmdo/config.py +201 -0
- cmdo_cli-0.1.0/src/cmdo/context.py +93 -0
- cmdo_cli-0.1.0/src/cmdo/display.py +134 -0
- cmdo_cli-0.1.0/src/cmdo/executor.py +103 -0
- cmdo_cli-0.1.0/src/cmdo/llm/__init__.py +0 -0
- cmdo_cli-0.1.0/src/cmdo/llm/client.py +56 -0
- cmdo_cli-0.1.0/src/cmdo/llm/parser.py +56 -0
- cmdo_cli-0.1.0/src/cmdo/llm/prompt.py +70 -0
- cmdo_cli-0.1.0/src/cmdo/models.py +65 -0
- cmdo_cli-0.1.0/src/cmdo/safety/__init__.py +0 -0
- cmdo_cli-0.1.0/src/cmdo/safety/classifier.py +72 -0
- cmdo_cli-0.1.0/src/cmdo/safety/forbidden.py +35 -0
- cmdo_cli-0.1.0/src/cmdo_cli.egg-info/PKG-INFO +136 -0
- cmdo_cli-0.1.0/src/cmdo_cli.egg-info/SOURCES.txt +28 -0
- cmdo_cli-0.1.0/src/cmdo_cli.egg-info/dependency_links.txt +1 -0
- cmdo_cli-0.1.0/src/cmdo_cli.egg-info/entry_points.txt +2 -0
- cmdo_cli-0.1.0/src/cmdo_cli.egg-info/requires.txt +14 -0
- cmdo_cli-0.1.0/src/cmdo_cli.egg-info/top_level.txt +1 -0
- cmdo_cli-0.1.0/tests/test_classifier.py +94 -0
- cmdo_cli-0.1.0/tests/test_context.py +22 -0
- cmdo_cli-0.1.0/tests/test_forbidden.py +34 -0
- cmdo_cli-0.1.0/tests/test_parser.py +55 -0
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.
|
cmdo_cli-0.1.0/PKG-INFO
ADDED
|
@@ -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
|
cmdo_cli-0.1.0/README.md
ADDED
|
@@ -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"]
|
cmdo_cli-0.1.0/setup.cfg
ADDED
|
@@ -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
|