Metadata-Version: 2.4
Name: polycmd
Version: 0.17.0
Summary: Compile POSIX, CMD, and PowerShell commands into safe, portable Python operations.
License: MIT
License-File: LICENSE
Keywords: cli,shell,posix,cmd,powershell,terminal,cross-platform
Author: Ryan Julyan
Author-email: ryan@julyan.biz
Requires-Python: >=3.11,<4.0
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Shells
Classifier: Typing :: Typed
Project-URL: Documentation, https://github.com/systemizing-solutions/polycmd#readme
Project-URL: Homepage, https://pypi.org/project/polycmd/
Project-URL: Issues, https://github.com/systemizing-solutions/polycmd/issues
Project-URL: Repository, https://github.com/systemizing-solutions/polycmd
Description-Content-Type: text/markdown

# Polycmd

Polycmd is a shared multi platform syntax, zero dependency python based, terminal that runs a practical subset of POSIX, Windows CMD, and PowerShell commands through one safe, portable Python execution model.

**current_version** = "v0.17.0"

Commands are compiled into canonical operations before execution. Supported
commands are not forwarded to a native shell, keeping behavior consistent across
Windows, macOS, and Linux.

## Quick start

### Install

```console
pip install polycmd
```

Polycmd supports Python 3.11, 3.12, and 3.13.

### Open the lightweight terminal

```console
polycmd terminal
```

The standard-library Tkinter terminal accepts commands without a `polycmd`
prefix:

```text
ls -la
dir /s /b *.py
Get-ChildItem . -Force
cat app.log | grep error > errors.txt
```

It keeps one Polycmd session alive, including its virtual working directory and
command history. The dialect selector appears on the left of the command path
and offers Auto, POSIX, CMD, and PowerShell; Auto is selected by default. Enter
runs a command, Up and Down navigate history, Ctrl+L clears the display, and
Ctrl+Q exits.

Wildcard directory listings are expanded inside Polycmd rather than by the host
shell, so they behave consistently in the terminal on every platform:

```text
ls *.md*
dir /s /b *.py
Get-ChildItem docs/*.md
```

Tkinter is included with standard CPython installers for Windows and macOS.
Some minimal Linux distributions package Tk separately.

### Get help

Display catalog-backed help from the command line:

```console
polycmd --help
polycmd help ls
polycmd ls --help
polycmd help fs.copy
```

Use the same forms inside the lightweight terminal without a `polycmd` prefix:

```text
help
help ls
ls --help
help dir
help Get-ChildItem
help fs.copy
```

Help is generated from the live command and operation catalogs. It includes
plugin commands, resolves aliases, and shows every dialect when a name has more
than one meaning. Command providers can supply a description, usage syntax, and
examples; otherwise Polycmd uses the compiler function's docstring.

## Why Polycmd?

Applications frequently need command-like behavior without depending on the
host operating system's shell syntax, quoting rules, or installed utilities.
Polycmd separates command syntax from execution:

```text
POSIX / CMD / PowerShell command
              ↓
       Dialect strategy
              ↓
       Canonical Polycmd IR
              ↓
       Registered operation
              ↓
       Portable Python service
```

This provides:

- one API for POSIX, CMD, and PowerShell input;
- conservative automatic dialect detection;
- portable filesystem, text, and environment operations;
- composable pipelines, redirects, conditionals, and sequences;
- filesystem confinement and explicit destructive-operation metadata;
- disabled-by-default external process execution;
- open registries for third-party dialects, commands, and operations;
- catalog-driven serialization and capability discovery.

Polycmd is a compatibility runtime, not a complete Bash, CMD, or PowerShell
emulator. Unsupported syntax fails explicitly.

## Command-line usage

Run a command using automatic dialect detection:

```console
polycmd "ls -la ./data"
polycmd "dir /s /b *.py"
polycmd "Get-ChildItem ./data -Force"
```

Compile a command into canonical JSON without executing it:

```console
polycmd compile --dialect posix "cat app.log | grep error > errors.txt"
```

Execute a previously serialized program:

```console
polycmd execute program.json
```

Inspect installed capabilities:

```console
polycmd capabilities
```

## Python API

```python
from pathlib import Path

from polycmd import Shell, ShellConfig


shell = Shell(
    ShellConfig(
        root=Path("./workspace"),
        discover_plugins=False,
    )
)

result = shell.run("Get-ChildItem . -Force")
print(result.stdout)
```

Select a dialect explicitly when required:

```python
program = shell.compile(
    "cat app.log | grep error > errors.txt",
    dialect="posix",
)

result = shell.execute(program)
```

Serialize and replay the same canonical program:

```python
payload = shell.serialization.dumps(program)
restored = shell.serialization.loads(payload)

assert restored == program
shell.execute(restored)
```

## Supported command surface

| Capability | Examples |
| --- | --- |
| Filesystem | list, current directory, change directory, create, copy, move, delete, touch, find |
| Text | read, write, search, head, tail, sort, unique, count |
| Environment | list and retrieve variables |
| Composition | pipelines, `>`, `>>`, `;`, `&&`, `||` |
| Program data | canonical JSON serialization and replay |
| Dialects | POSIX, CMD, PowerShell, and registered third-party dialects |

For example, these commands compile to equivalent canonical operations:

```text
cat app.log
type app.log
Get-Content app.log
```

## Shell integration

Polycmd can expose registered operation and dialect command names through the
normal system PATH:

```console
polycmd shell install
polycmd shell status
polycmd shell uninstall
```

Restart the terminal after installation. On Zsh, Polycmd installs managed
`noglob` aliases so foreign-dialect wildcard syntax reaches Polycmd unchanged:

```console
dir /s /b *.py
```

Commands without a registered Polycmd shim continue through the native PATH.
When an entire compound expression must be compiled by Polycmd, use the Tkinter
terminal or the explicit `polycmd "..."` form. Otherwise, operators such as `|`
and `>` remain owned by the active host shell.

## Configuration and security

Relative paths are confined beneath `ShellConfig.root`. Polycmd maintains a
virtual current directory and never changes the host process directory with
`os.chdir()`.

External process execution is disabled by default. Enabling it requires both
`allow_external=True` and an exact executable name in `external_allowlist`:

```python
from pathlib import Path

from polycmd import Shell, ShellConfig


shell = Shell(
    ShellConfig(
        root=Path("./workspace"),
        allow_external=True,
        external_allowlist=("python",),
    )
)
```

External arguments are passed as an array with `shell=False`. Polycmd does not
fall back to executing unsupported input through a native shell.

## Extending Polycmd

### Add or replace a dialect command

```python
from polycmd import CommandDefinition
from polycmd.ir import WriteText


def compile_hello(invocation):
    return WriteText(text=" ".join(invocation.argv))


shell.register_command(
    "posix",
    CommandDefinition("hello", compile_hello),
)
```

Existing names are protected unless `replace=True` is explicitly supplied.

### Add an operation

```python
from dataclasses import dataclass

from polycmd.ir import Operation
from polycmd.registries import OperationDefinition


@dataclass(frozen=True, kw_only=True)
class CompressArchive(Operation):
    source: str
    destination: str


context.operations.register(
    OperationDefinition(
        operation_id="archive.compress",
        operation_type=CompressArchive,
        handler=compress_archive,
        shell_aliases=("compress",),
    )
)
```

The operation catalog owns execution, serialization identity, destructive
metadata, capabilities, and shell names. Plugins can register through the
`polycmd.plugins` entry-point group without changing Polycmd core.

More detail is available in:

- [Architecture](https://github.com/systemizing-solutions/polycmd/blob/main/docs/architecture.md)
- [Adding a dialect](https://github.com/systemizing-solutions/polycmd/blob/main/docs/adding-a-dialect.md)
- [Adding a command](https://github.com/systemizing-solutions/polycmd/blob/main/docs/adding-a-command.md)
- [Adding an operation](https://github.com/systemizing-solutions/polycmd/blob/main/docs/adding-an-operation.md)
- [Release procedure](https://github.com/systemizing-solutions/polycmd/blob/main/docs/releasing.md)

## Development

Clone the repository and install the development environment:

```console
git clone https://github.com/systemizing-solutions/polycmd.git
cd polycmd
poetry install
```

Run the quality gates:

```console
poetry run pytest
poetry run ruff check src/polycmd tests
poetry run ruff format --check src/polycmd tests
poetry run mypy
```

### BumpVer

Preview a version change before applying it:

```console
poetry run bumpver update --patch --dry
poetry run bumpver update --minor --dry
poetry run bumpver update --major --dry
```

Apply a release bump:

```console
poetry run bumpver update --patch
```

BumpVer updates `pyproject.toml`, `src/polycmd/__init__.py`, and this README,
creates the version commit and tag, and pushes them. Pushing a semantic version
tag starts the PyPI publication workflow.

### Build locally

```console
poetry check
poetry build
```

The build produces a source distribution and a universal Python wheel in
`dist/`.

## Automated PyPI publishing

The `publish.yml` workflow publishes semantic version tags through PyPI trusted
publishing. No PyPI API token is stored in GitHub.

One-time repository setup:

1. Create the `polycmd` project or a pending trusted publisher on PyPI.
2. Set the GitHub owner to `systemizing-solutions`.
3. Set the repository to `polycmd`.
4. Set the workflow filename to `publish.yml`.
5. Set the environment name to `pypi`.
6. Create a matching GitHub environment named `pypi`.

For every tagged release, GitHub Actions verifies that the tag equals the Poetry
version, validates package metadata, runs linting, typing, and tests, builds both
distribution formats, and publishes them using OpenID Connect.

## License

Polycmd is released under the MIT License. See
[LICENSE](https://github.com/systemizing-solutions/polycmd/blob/main/LICENSE).

## Credits

Author: Ryan Julyan

