Metadata-Version: 2.4
Name: solidx-sysadm
Version: 0.1.0
Summary: An embeddable macOS and Linux system administration agent harness
License: MIT
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: mini-swe-agent<2.5,>=2.4.6
Requires-Dist: litellm[mcp]==1.106.0
Requires-Dist: jsonschema<5,>=4.20
Requires-Dist: rich<15,>=13.7
Requires-Dist: textual<2,>=0.80
Provides-Extra: test
Requires-Dist: pytest<10,>=8; extra == "test"
Dynamic: license-file

# sysadm

A macOS and Linux system-administration agent harness. The main interface is a
full-screen, streaming terminal UI (TUI) with a conversation log, live status,
and a persistent input bar. The CLI manages configuration and extensions.

The harness uses **mini-swe-agent** for the query/action lifecycle and
**LiteLLM** for model requests. The reviewed command gateway is exposed through
the built-in bash tool.

## Try the actual harness without a model key

Dependencies are installed in this checkout's .venv. From this directory:

~~~console
./sysadm --demo
~~~

Enter a message, answer the confirmation, then report a result or type skip.
This explicitly labelled offline demo uses the real mini-swe-agent loop, gateway,
event stream, human gates, and checkpoints with a scripted model adapter.
It does not call a paid model API. Its example directory-creation command is
displayed only; the demo never executes it.

For a fresh installation:

~~~console
python3 -m venv .venv
.venv/bin/python -m pip install -e .
.venv/bin/sysadm --version
~~~

The tag-based GitHub Actions release SOP is in [docs/RELEASE.md](docs/RELEASE.md).
The isolated install procedure for a remote Linux machine is in
[the first-run guide](docs/releasing.md).

The installed executable is **sysadm**, replacing the generated bash-- entrypoint.
The source launcher automatically uses this checkout's .venv when available.
Python 3.11+ is required. Native inspection runs on macOS and Linux.

## Configure models

~~~console
./sysadm --configure
~~~

The wizard asks for main and fast models, provider IDs, streaming behavior,
provider endpoints, and API key environment variable names. You can instead
import a complete JSON configuration with `--configure --config-file PATH`.

A noninteractive example, replacing the model IDs with models available to you:

~~~console
./sysadm --configure \
  --fast-model openai/YOUR_FAST_MODEL \
  --fast-key-env SYSADM_FAST_API_KEY \
  --main-model openai/YOUR_MAIN_MODEL \
  --main-key-env SYSADM_MAIN_API_KEY
~~~

Set those environment variables in your terminal before starting the agent.
The configuration stores model profiles separately from reusable providers:

~~~json
{
  "schema_version": 2,
  "models": {
    "main": {"providerId": "openrouter", "model": "openai/gpt-6-luna", "behavior": {"streaming": false}},
    "fast": {"providerId": "openrouter", "model": "openai/gpt-6-luna", "behavior": {"streaming": false}}
  },
  "providers": {
    "openrouter": {
      "type": "openai-compatible",
      "apiKeyEnv": "OPENROUTER_API_KEY",
      "baseUrl": "https://openrouter.ai/api/v1"
    }
  }
}
~~~

The [OpenRouter API reference](https://openrouter.ai/docs/api/api-reference/chat/send-chat-completion-request)
confirms that base URL. You may use `apiKey` instead of `apiKeyEnv` to store the
key inline; `config.json` is private (mode 0600), and `--show-config` redacts it.
An environment variable keeps the value out of the config file. Remote base URLs
require HTTPS, while loopback HTTP is allowed. `streaming: false` returns one
answer event after inference; `true` emits text as it arrives. Both modes retain
tool and human-interaction events in the TUI or line-oriented REPL.
OpenAI-compatible providers use
LiteLLM's `openai/` adapter internally while sending the configured model ID
unchanged to the provider. The older schema is migrated automatically.

~~~console
./sysadm --show-config
./sysadm --configure --config-file examples/sysadm-openrouter-env.json
~~~

The environment example references `SYSADM_REASONING_API_KEY`, as used in the
earlier setup. [The inline-key example](examples/sysadm-config.json) demonstrates
the `apiKey` field with a placeholder; replace that placeholder before importing.
The main profile is the default. The fast profile is available as a
consult_fast_model tool with no tool access of its own. You may explicitly switch
the main session to fast with /model fast.

## Work in the terminal UI

~~~console
./sysadm
~~~

Ask a normal question, such as “Inspect this machine and tell me which diagnostics
would help investigate low disk space.”

The TUI renders streamed answers, reviewed commands and their output, approval
cards, and status in a scrollable conversation. Approval replies go through the
same input bar. When a separately approved command needs sudo, sysadm suspends
the full-screen interface so sudo can securely read its password from the
controlling terminal, then restores the TUI when the operation and agent turn
finish.

Use `--plain` for the line-oriented REPL, or `--json` for NDJSON events. Both
remain available for scripts, redirected terminals, and troubleshooting.

| TUI control | Purpose |
| --- | --- |
| /help | Show controls. |
| /model fast or /model main | Select this session's model profile. |
| /skills, /tools | Inspect extension catalogs. |
| /commands | Show globally approved command forms. |
| /trust-paths | List saved executable-path trust approvals. |
| /untrust-path ID | Revoke a saved executable-path trust approval. |
| /status, /sessions | Inspect local session state. |
| /new, /resume UUID | Start or resume a conversation. |
| /cd PATH | Change this session's working directory without changing the host process directory. |
| /exec COMMAND | Run a globally or locally approved command directly. |
| /check COMMAND | Classify a command without executing it. |
| /answer REQUEST_ID TEXT | Answer a specific pending question. |
| /history, /history clear | Review or erase local prompt history. |
| /copy | Copy the most recently shown command to the clipboard. |
| /cancel | Cancel pending and queued work. |
| /exit | Leave; pending work stays resumable. |

Normal text answers the current pending request when the agent is paused.
Confirmations require an explicit yes/no response. The TUI saves up to 500
recent prompts in the private `input-history.json` file under the sysadm home
directory. Use Up/Down to browse, Ctrl+R to search, `/history clear` to clear
saved entries, or `--no-history` to keep entries in memory only. Approval replies,
`/exec` commands, and `/answer` payloads are excluded. EOF leaves a parked
session on disk; Ctrl+C requests cancellation. Sanitized conversation history
and events are stored as described below.

While an approval is open, use Y/N to approve or deny, O to approve this run,
and R to remember an executable directory when that option is offered. Choices
are case-insensitive. Press C to copy the command shown in the request; `/copy`
copies the most recently shown command.
For questions with a list of choices, each option is shown on its own line with
a unique key; pressing that key submits the complete matching option.

There is no task-execution CLI mode: positional prompts are rejected.
Work, inspection, and agent interactions happen inside the TUI, plain REPL, or
SDK. Use --json for NDJSON harness events; UI control responses are separate
ui.notice records. Gateway options and commands are documented in
[the gateway reference](docs/gateway.md).

## Local configuration and sessions

The existing SolidX naming convention is followed:

~~~text
~/.solidx/sysadm/
  config.json
  system_prompt.md
  input-history.json  # TUI prompt history unless --no-history is used
  skills/<name>/SKILL.md
  skill-drafts/<name>/SKILL.md
  tools/<name>.json
  policy/policy-state.json
  policy/local-allowlist.json
  sessions/<uuid>/
    checkpoint.json
    events.jsonl
    turn.lock
~~~

Override the directory with `--home PATH` or `SYSADM_HOME`.

Checkpoints include conversation messages, model role, counters/cost, working
directory, pending human request, and remaining tool calls. They are atomically
replaced. A turn lock prevents two processes from advancing the same session.
Interrupted running turns are closed out rather than automatically retrying tools.

~~~console
./sysadm --resume SESSION_UUID
~~~

Configuration, checkpoints, and event files are created with private permissions.
Known model keys and recognizable credential fields are redacted at model/event/
persistence boundaries. Do not paste secrets into chat. Redaction is not a
general-purpose secret scanner, and conversation files remain sensitive.

State is local by default. Model requests send the conversation, active prompt,
skill catalog/loaded skills, and selected tool results to your configured provider.
Choose the provider and local retention settings accordingly.

## Skills and tools

~~~console
./sysadm --add-skill examples/skills/local-diagnostics
./sysadm --list-skills
./sysadm --remove-skill local-diagnostics
./sysadm --list-skill-drafts
./sysadm --add-skill-draft deployment-estate

./sysadm --add-tool examples/tools/read_head.json
./sysadm --add-tool examples/tools/create_directory_instruction.json
./sysadm --list-tools
./sysadm --remove-tool read_head

./sysadm --set-system-prompt /path/to/system_prompt.md
~~~

Start a new session or restart sysadm to pick up configuration/extension changes.
Skills are Markdown procedures with optional local reference files. They are not
executed as code. The agent lists skill descriptions and loads a relevant body
on demand.

To author an operational knowledge skill, ask in the TUI or REPL for a
draft—for example,
“Create a deployment-estate skill from this conversation and the NGINX/PM2 files
we inspect; distinguish verified facts from assumptions.” The agent can inspect
local evidence through the existing command gateway, then `save_skill_draft`
writes a Markdown draft under `skill-drafts/`. Drafts are not loaded by the agent.
Review the file, edit it as needed, and explicitly install the reviewed draft with
`sysadm --add-skill-draft deployment-estate`. The installed copy appears under
`skills/` and can be packaged and shared with other developers using `--add-skill`.
The draft records evidence notes, but those notes are not independent verification;
review claims and remove sensitive details before installing or sharing.

Installed tools are validated **declarative manifests**:
- command tools bind scalar arguments into an argv template and go through the
  same reviewed gateway as the built-in bash tool;
- instruction tools display a command for another terminal and park the session;
- optional confirmation gates can protect command tools.

Installing a skill or declarative tool does not import or execute plugin code.
Commands explicitly approved by the user can run scripts as executables. Reserved
built-in names cannot be replaced. A skill or tool manifest cannot approve a
command: the gateway still requires a global grant or the user's approval of one
exact invocation. See [extension contracts](docs/extensions.md).

### Agent-requested commands

The agent can request any single executable invocation not already enabled by
global policy. The TUI or REPL shows the exact command and asks before saving
or running it. Local approval applies only to that exact executable and argument vector,
survives remote policy refreshes, and can be revoked. Explicit global denials
always win. The runner does not parse shell operators; if a compound command is
needed, the agent may request an explicit shell invocation such as `sh -c '...'`,
which makes the full script visible in the approval prompt. Review all arguments
carefully: an approved command can modify files or services, use elevated access,
or expose sensitive output.

For example, ask sysadm to investigate disk usage. It can run the globally
enabled `df -h` and `du` forms, then request `pm2 list` if needed. A different
argument vector requires a separate approval unless the global policy contains
a broader form.

Inspect or revoke local approvals with:

~~~console
sysadm --list-local-commands
sysadm --remove-local-command APPROVAL_ID
sysadm --list-local-paths
sysadm --remove-local-path PATH_APPROVAL_ID
~~~

Executable directory trust is separate from command approval. When a path check
fails, approve it for one run or choose `remember` to save that exact directory
for the current operating system. Review saved entries with `/trust-paths` or
`sysadm --list-local-paths`; revoke one with `sysadm --remove-local-path ID`.

Commands needing interactive input can still be presented for you to run in
another SSH terminal. The harness waits for your report and will not claim that
it ran them.

Built-in tools are bash, request_command_access, list_commands, list_skills,
load_skill, consult_fast_model, request_human_input, request_confirmation, and
instruct_user.

Limits cover session steps, steps per turn, active turn time, context size, model
response size, and tool-output size. Cost is tracked when LiteLLM can price the
model; unknown pricing is not treated as a reliable spend cap.
Model calls have timeouts and a bounded retry count. No model API call is made by
configuration, extension-management commands, or the offline demo.

This remains a policy gateway, **not an OS sandbox**. It trusts installed
utilities and their dependencies. Reads/diagnostics can have incidental cache,
audit, automount, and access-time effects. Cancellation is cooperative between
model chunks/steps and subject to child-process limits. See the gateway reference.

## Embedding the harness

~~~python
from sysadm import SysadmSession

session = SysadmSession()  # ~/.solidx/sysadm
session.subscribe(lambda event: print(event.as_dict()))
session.run_turn("Inspect disk usage and explain your findings.")

if session.pending:
    request_id = session.pending["id"]
    # Deliver this request to your UI and wait for a real human answer.
    session.answer(request_id, "the human's actual answer")
~~~

A pull-based adapter is also available:

~~~python
for event in session.stream_turn("Inspect the current environment."):
    deliver_to_ui(event.as_dict())
~~~

Both adapters expose the same versioned event contract. A future web UI or
outer agent loop can subscribe and send answers by request ID. See
[architecture and event lifecycle](docs/architecture.md).

## Development and verification

~~~console
.venv/bin/python -W error::ResourceWarning -m unittest discover -s tests -v
~~~

Tests exercise the actual mini-swe-agent lifecycle with scripted provider
boundaries, real macOS inspection binaries, local persistence, pause/resume,
remaining tool calls, limits, extensions, redaction, and provider streaming
assembly. Live paid-model inference requires your own configured model IDs and
credentials. Linux execution is covered by the CI workflow.
