Metadata-Version: 2.4
Name: msdev
Version: 0.14.0
Summary: Portable nodes and execution environments for msModelSlim development
Author: msModelSlim Agent Contributors
License-Expression: MulanPSL-2.0
Project-URL: Homepage, https://gitcode.com/rookie_hongchuan/msdev
Project-URL: Repository, https://gitcode.com/rookie_hongchuan/msdev
Keywords: ssh,docker,conda,venv,remote,npu,mlops,devops
Classifier: Environment :: Console
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Topic :: Software Development :: Build Tools
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# msdev

`msdev` exposes three capabilities: **`node`** (connect + host agent),
**`env`** (shared execution context, shell, files), and **`exec`** (commands).

A Node owns SSH reachability and `msdevd` deployment. An Env owns the full
execution context: host or Docker runtime, conda/venv/uv layers, optional
`setup` script (root switch, proxy, etc.), command `wrap`, and static env vars.

The CLI is intentionally stateless: execution always names the env as the first
`exec` argument.
MCP is not part of the architecture.

## Install

```bash
git clone https://gitcode.com/rookie_hongchuan/msdev.git
cd msdev
python3 -m pip install -e .
msdev --help
```

The package installs `msdev` and `msdevd`.

Install the agent skill for Cursor and Claude Code, and sync SSH config:

```bash
msdev init
msdev init --scope project
msdev init -f
```

`init` registers Host aliases from `~/.ssh/config`, prints added/removed
changes, and writes `skills/msdev-cli/SKILL.md` into `~/.cursor/skills/` and
`~/.claude/skills/` (or into `.cursor/` / `.claude/` under the current
directory with `--scope project`). Outdated skill files prompt before
overwrite; use `-f` to force.

## Windows client

The typical setup is a **Windows laptop talking to Linux Nodes** via OpenSSH
(`C:\Windows\System32\OpenSSH\ssh.exe`). Native Windows OpenSSH does not
implement ControlMaster multiplexing, so msdev:

- omits `ControlMaster`/`ControlPath` on Windows (override with
  `MSDEV_SSH_CONTROL_MASTER=1` only if your SSH client actually supports mux);
- starts a persistent `ssh -L` LocalForward tunnel automatically on the first
  RPC (same role as ControlMaster). Explicit `msdev node connect NODE` is
  optional; disable persist with `MSDEV_SSH_LOCAL_FORWARD_PERSIST=0`;
- reuses that tunnel for captured RPC and default streaming `msdev exec`;
  `env put`/`get` stay on sftp/ssh streams (file bytes do not go through JSON RPC);
- hides SSH console windows (`CREATE_NO_WINDOW`) and kills process trees with
  `taskkill /T` on timeout or cancel;
- SSH is non-interactive: `BatchMode` unless a password is supplied, host keys
  use `accept-new`, and there is no password/passphrase/yes-no prompt. When a
  password is known, injects OpenSSH `SSH_ASKPASS` so `node connect` (and later
  ssh/sftp) can authenticate without a TTY; the local public key is then
  installed on the Node so later logins are passwordless. The password is
  never written to the node registry;
- serves a TCP loopback endpoint for local `msdevd` when this Python build has
  no `AF_UNIX` sockets.

Install with `python -m pip install -e .`. `install-user.py` writes
`msdev.cmd` / `msdevd.cmd` under `%USERPROFILE%\.local\bin`.

Remote paths inside a Linux Env remain POSIX (`/srv/...`). Local `env put`/`get`
on Windows accept Windows absolute paths (`C:\...`).

Local `exec` on Windows wraps Conda with `conda run` and venvs with
`Scripts\activate.bat` (remote Linux Nodes still use POSIX `sh`/`bash` wrappers).
If `ssh` is not on PATH, msdev looks in `%SystemRoot%\System32\OpenSSH\`.

## Nodes and execution environments

Use **`node connect`** as the single entry point: it writes `~/.ssh/config`
when needed, registers the node, saves passwords, installs the local public
key, and deploys `msdevd`:

```bash
msdev node connect dev-122 --hostname 80.48.33.133 --user root --password PASSWORD
msdev node connect dev-122
msdev node bootstrap dev-122
msdev node sync-ssh
msdev node list --ssh
msdev node list
```

Connect writes or updates a dedicated `Host <name>` block in OpenSSH config.
The msdev registry stores only the Host alias plus msdevd settings
(`remote_bin`, `auto_bootstrap`). User, port, keys, and proxy remain in SSH
config. `node sync-ssh` registers Host aliases that were added manually.

Connect atomically creates a same-named host Env on first use and uploads a
**static Go msdevd** binary matching the node architecture
(`linux-amd64` / `linux-arm64`) when the agent is missing or when the remote
`ping` version does not match the local CLI. Use **`msdev node bootstrap NODE`**
to force a redeploy after upgrading msdev. No Python is required on the node.

Build the static daemons before release or local testing:

```bash
bash msdevd/build-static.sh
bash file-helper/build-static.sh
```

Packaged artifacts live under `src/msdev/helpers/` (gitignored until built).
The Go source is in `msdev/`.

Persistent OpenSSH masters are optional. All SSH from msdev is
non-interactive: host keys use `accept-new`, and password/passphrase prompts
are disabled. Supply `--password` on connect; it is saved to ``~/.ssh/config``
as ``# msdev Password ...`` and used to install the local public key
(``~/.ssh/id_ed25519``, generated if missing) so later connections can be
passwordless:

```bash
msdev node connect dev-122 --hostname 80.48.33.133 --user root --password PASSWORD
msdev node connect dev-122
msdev node connect dev-122 --identity FILE     # choose which key to install
msdev node connect dev-122 --no-install-key    # password only, do not copy key
msdev node disconnect dev-122
```

Nodes and Envs have separate private Markdown guides:

```bash
msdev node guide dev-122
msdev node guide dev-122 --write ./dev-122-guide.md
msdev env guide dev-122
msdev env guide dev-122 --write ./host-env-guide.md
```

Guides are stored under `~/.config/msdev/guides/nodes/` and
`~/.config/msdev/guides/environments/`. **Node guides** document how to
connect; **Env guides** document how to initialize shared context (`setup`).

Share a full local configuration (registry + guides) with:

```bash
msdev config export ./msdev-config.tar.gz
msdev config import ./msdev-config.tar.gz
msdev config import ./msdev-config.tar.gz --force   # overwrite name conflicts
```

Bundles contain `resources.json`, both guide trees, and a `manifest.json`.
SSH keys and passwords are never exported.

```bash
msdev env add dev-122-root \
  --node dev-122 \
  --setup-file ./setup-root.sh \
  --wrap sudo --wrap -n --wrap --

msdev env open dev-122-root
```

Open a human-operated interactive shell in an Env:

```bash
msdev env shell dev-122
msdev env shell dev-122 --cwd /srv/project
```

Agent automation should use `msdev exec`, not `env shell`.

## File put, get, read, and write

Bulk transfer copies a local file into an Env filesystem, or fetches one back:

```bash
msdev env put dev-122 ./config.yaml /srv/project/config.yaml
msdev env get dev-122 /srv/project/config.yaml --output ./config.yaml
msdev env get dev-122 /srv/project/config.yaml > ./config.yaml
```

Editing uses content-oriented read/write. Paths inside the Env must be
absolute. Host Envs use direct filesystem copy locally and ``sftp``/``ssh``
streams remotely. Docker Envs stage through the node with ``docker cp``.
File bytes never pass through JSON RPC.

```bash
msdev env read dev-122 /srv/project/config.yaml
msdev env read dev-122 /srv/project/config.yaml --json
msdev env write dev-122 /srv/project/note.md --file /tmp/note.md
msdev env write dev-122 /srv/project/note.md < /tmp/note.md
msdev env write dev-122 /srv/project/note.md --file /tmp/note.md \
  --expected-sha256 <digest>
```

Read before replacing an existing file. `--expected-sha256` is compare-and-swap
replacement that refuses to overwrite a changed remote file.

## Command execution

Commands execute synchronously and stream stdout and stderr to the terminal as
they are produced. Cursor or Claude Code should use their native background
terminal support for long jobs.

Pick **one** form:

| Scenario | Form |
|----------|------|
| Simple one-liner | `-c '…'` |
| Quotes, pipes, multi-line | `--file -` + heredoc (see below) |
| Script on disk | `--file ./script.sh` |

**Heredoc** (`<<'EOF'`) is a local-shell mechanism: it writes a multi-line
string to msdev's **stdin** when you pass `--file -`. Quote the delimiter
(`<<'EOF'`) so `$` and backticks are not expanded locally. msdev then runs the
body through a remote shell (`sh -c`), or via shebang if the body starts with
`#!`.

```bash
msdev exec dev-122 --file - <<'EOF'
python3 -c 'print("no local quoting")'
grep 'pattern' /var/log/app.log | tail -20
EOF

msdev exec dev-122 -c 'python3 train.py --config "path with spaces" | tee log.txt'

msdev exec dev-122 --file ./scripts/run.sh
```

Positional commands after `--` are not supported. The env name is the first
argument after `exec`; use `-c` for inline strings or `--file` for script
content (path or `-` for stdin/heredoc). `-c -` is accepted but identical to
`--file -`; prefer **`--file -`** only.

Quoted pipelines and `|` tokens are executed through a remote POSIX shell
(`sh -c`), so they run on the Node instead of being treated as a single
executable name. On a Windows `local` Env they use `cmd.exe /s /c`. Prefer
`sh -c '…'` for complex scripts. Default streaming writes POSIX newlines as
CRLF on a Windows console so remote line output does not staircase; piped
CLI output keeps LF.

`--timeout-seconds` defaults to `-1` (no deadline). Any positive finite duration
is also accepted. A positive timeout kills the complete command
process group and returns exit code 124. Cancelling the local CLI closes the RPC
connection, causing `msdevd` to kill and reap the remote process group. Finite
SSH/Unix RPC deadlines include a short completion grace for output drain and
serialization.

`--result-json` selects captured, non-streaming execution and prints one
structured JSON result containing stdout and stderr.

## Architecture

```text
msdev CLI
  -> typed core service
  -> UnixRpcTransport or SshRpcTransport
  -> per-user msdevd (env session + job runner + files)
```

Remote RPC responses and command output are bounded. Node/Env registries use
locked atomic updates.

## Testing

```bash
python3 -m unittest discover -s tests -v
```

Feature work must include at least one actual scenario test on the real
control path (`msdev` CLI → service → transport → `msdevd` → effect). Unit
tests alone are not sufficient.
