xcorecli 2.2.0__tar.gz → 2.3.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.
- {xcorecli-2.2.0 → xcorecli-2.3.0}/PKG-INFO +1 -1
- {xcorecli-2.2.0 → xcorecli-2.3.0}/docs/commands/health.md +3 -1
- {xcorecli-2.2.0 → xcorecli-2.3.0}/docs/commands/init.md +13 -5
- xcorecli-2.3.0/docs/config/index.md +48 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/docs/getting-started/configuration.md +1 -1
- {xcorecli-2.2.0 → xcorecli-2.3.0}/docs/getting-started/install.md +1 -1
- {xcorecli-2.2.0 → xcorecli-2.3.0}/docs/index.md +1 -1
- {xcorecli-2.2.0 → xcorecli-2.3.0}/docs/migration/index.md +17 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/docs/plugin/install.md +26 -2
- {xcorecli-2.2.0 → xcorecli-2.3.0}/docs/plugin/marketplace.md +17 -2
- {xcorecli-2.2.0 → xcorecli-2.3.0}/docs/plugin/security.md +29 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/docs/reference.md +35 -0
- xcorecli-2.3.0/docs/sandbox/index.md +86 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/pyproject.toml +1 -1
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/migrations/cli.py +105 -12
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/migrations/runtime.py +37 -3
- xcorecli-2.2.0/docs/config/index.md +0 -49
- xcorecli-2.2.0/docs/sandbox/index.md +0 -43
- {xcorecli-2.2.0 → xcorecli-2.3.0}/.github/workflows/publish.yml +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/.gitignore +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/.python-version +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/.vscode/configurationCache.log +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/.vscode/dryrun.log +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/.vscode/settings.json +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/.vscode/targets.log +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/README.md +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/docs/commands/login.md +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/docs/getting-started/auth.md +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/docs/manager/index.md +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/docs/manager/monitoring.md +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/docs/manager/services.md +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/docs/plugin/index.md +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/docs/plugin/local.md +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/docs/plugin/runtime.md +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/docs/plugin/update.md +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/docs/service/index.md +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/docs/worker/index.md +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/docs/worker/process.md +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/makefile +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/mkdocs.yml +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/uv.lock +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/__init__.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/_credentials.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/_login.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/_run.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/_xcore.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/config/__init__.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/config/cli.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/config/runtime.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/init/__init__.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/init/manager.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/init/upgrade.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/main.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/manager/__init__.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/manager/cli.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/migrations/__init__.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/plugin/__init__.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/plugin/cli.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/plugin/install_commands.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/plugin/local_commands.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/plugin/marketplace_commands.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/plugin/runtime_commands.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/plugin/scaffold.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/plugin/security_commands.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/plugin/shared.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/plugin/update_commands.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/sandbox/__init__.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/sandbox/cli.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/service/__init__.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/service/cli.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/service/install_commands.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/service/marketplace_commands.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/service/shared.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/worker/__init__.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/worker/cli.py +0 -0
- {xcorecli-2.2.0 → xcorecli-2.3.0}/xcli/worker/worker.py +0 -0
|
@@ -39,5 +39,7 @@ xcli services
|
|
|
39
39
|
|
|
40
40
|
If `health` reports errors:
|
|
41
41
|
1. Check the logs: `make logs`
|
|
42
|
-
2.
|
|
42
|
+
2. Check your marketplace credentials aren't the issue: `xcli config show`
|
|
43
|
+
(there's no dedicated `integration.yaml` validator — it's checked
|
|
44
|
+
automatically whenever `xcli manager start` reads it)
|
|
43
45
|
3. Restart services: `make restart`
|
|
@@ -12,11 +12,19 @@ xcli init my-project
|
|
|
12
12
|
|
|
13
13
|
### Database Options
|
|
14
14
|
|
|
15
|
-
The initialization wizard supports several database backends with
|
|
16
|
-
- **
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
- **
|
|
15
|
+
The initialization wizard supports several database backends with
|
|
16
|
+
pre-configured URL templates — always the **async** driver variant, never
|
|
17
|
+
a sync one, since `xcore` is async end-to-end:
|
|
18
|
+
|
|
19
|
+
- **SQLite** (default): `sqlite+aiosqlite:///./xcore.db`
|
|
20
|
+
- **PostgreSQL**: `postgresql+asyncpg://user:pass@localhost:5432/dbname`
|
|
21
|
+
- **MySQL**: `mysql+aiomysql://user:pass@localhost:3306/dbname`
|
|
22
|
+
- **MariaDB**: `mysql+aiomysql://user:pass@localhost:3306/dbname`
|
|
23
|
+
|
|
24
|
+
`--db-url` overrides the template entirely if you need a different
|
|
25
|
+
host/user/password — but keep the `+aiosqlite`/`+asyncpg`/`+aiomysql`
|
|
26
|
+
driver suffix; a plain `postgresql://`/`mysql://` URL uses a sync driver
|
|
27
|
+
that doesn't work with this framework's async SQLAlchemy engine.
|
|
20
28
|
|
|
21
29
|
### Generated Structure
|
|
22
30
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# CLI Configuration
|
|
2
|
+
|
|
3
|
+
The `config` command group stores the two credentials `xcorecli` needs to
|
|
4
|
+
talk to the marketplace: an **API key** (`xdk_...`, authorizes downloads)
|
|
5
|
+
and a **signing key** (verifies the HMAC signature of every ZIP before
|
|
6
|
+
extraction). It does **not** manage `integration.yaml` or any other
|
|
7
|
+
project-level setting — see [Getting started → Configuration](../getting-started/configuration.md)
|
|
8
|
+
for that.
|
|
9
|
+
|
|
10
|
+
!!! tip "Prefer `xcli login`"
|
|
11
|
+
`xcli login` (device-code flow, opens a browser) fetches and stores
|
|
12
|
+
**both** credentials in one step, without ever printing them to the
|
|
13
|
+
terminal. `config set` below is the manual fallback — useful in a
|
|
14
|
+
non-interactive environment (CI, a container) where opening a browser
|
|
15
|
+
isn't possible.
|
|
16
|
+
|
|
17
|
+
## Commands
|
|
18
|
+
|
|
19
|
+
| Command | Description |
|
|
20
|
+
|---------|-------------|
|
|
21
|
+
| `xcli config set <key> <value>` | Store a credential — `key` must be `api-key` or `signing-key`. |
|
|
22
|
+
| `xcli config show` | Print whether each credential is set (values are masked, never shown in full). |
|
|
23
|
+
|
|
24
|
+
## Setting credentials manually
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
xcli config set api-key xdk_...
|
|
28
|
+
xcli config set signing-key <your-signing-secret>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Both are written to `~/.xcli/config.json` — the same file `xcli login`
|
|
32
|
+
writes to, so mixing the two approaches (login once, then manually rotate
|
|
33
|
+
one key later) is safe.
|
|
34
|
+
|
|
35
|
+
## Checking status
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
xcli config show
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
api-key: set
|
|
43
|
+
signing-key: not set
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Nothing else is configurable through this command group — there is no
|
|
47
|
+
`get`/`validate` sub-command, and no arbitrary `key.path value` form; `key`
|
|
48
|
+
is restricted to exactly `api-key` or `signing-key`.
|
|
@@ -67,4 +67,4 @@ observability:
|
|
|
67
67
|
|
|
68
68
|
## Validation
|
|
69
69
|
|
|
70
|
-
`xcorecli` validates this file upon startup to ensure all required fields are present and correctly typed
|
|
70
|
+
`xcorecli` validates this file upon startup to ensure all required fields are present and correctly typed — there is no separate command to check it manually ahead of time; `xcli manager start` (or any command that touches the running project) is itself the validation step. This is unrelated to [`xcli config`](../config/index.md), which only stores your marketplace API/signing keys, not `integration.yaml` settings.
|
|
@@ -13,7 +13,7 @@ Getting started with `xcorecli` is straightforward. The project uses [Poetry](ht
|
|
|
13
13
|
### 1. Clone the Repository
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
git clone https://github.com/
|
|
16
|
+
git clone https://github.com/xcore-team/xcoreCli.git
|
|
17
17
|
cd xcorecli
|
|
18
18
|
```
|
|
19
19
|
|
|
@@ -23,6 +23,15 @@ xcli migration init
|
|
|
23
23
|
|
|
24
24
|
This creates the `alembic/` directory, configuration files, and scans for initial models.
|
|
25
25
|
|
|
26
|
+
!!! tip "Custom directory name"
|
|
27
|
+
Pass `--dir my_migrations` to use a different directory name. The chosen
|
|
28
|
+
name is persisted to `migration.directory` in `integration.yaml`, so every
|
|
29
|
+
other `migration` command (`revision`, `upgrade`, `downgrade`, `current`,
|
|
30
|
+
`history`, `heads`, `stamp`) picks it up automatically — no need to repeat
|
|
31
|
+
`--dir` on each call. To rename an already-initialized project, use
|
|
32
|
+
`xcli migration rename <new_name>` instead of moving the folder by hand —
|
|
33
|
+
it also updates `alembic.ini` and `integration.yaml` for you.
|
|
34
|
+
|
|
26
35
|
## Safety & Backups
|
|
27
36
|
|
|
28
37
|
Before performing dangerous operations, `xcorecli` can automatically backup your database.
|
|
@@ -99,5 +108,13 @@ Check which migrations have been applied.
|
|
|
99
108
|
xcli migration history
|
|
100
109
|
```
|
|
101
110
|
|
|
111
|
+
See the database's current revision, or where the migration chain's
|
|
112
|
+
unapplied heads are:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
xcli migration current # what revision the DB is actually stamped at
|
|
116
|
+
xcli migration heads # latest revision(s) defined in the migration scripts
|
|
117
|
+
```
|
|
118
|
+
|
|
102
119
|
!!! tip "Stamping"
|
|
103
120
|
Use `xcli migration stamp <id>` to mark the database at a specific revision without running the actual migration scripts.
|
|
@@ -32,14 +32,38 @@ To see all available versions for a plugin:
|
|
|
32
32
|
xcli plugin versions name-of-plugin
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
## Installing from Git or
|
|
35
|
+
## Installing from Git or a Local Zip
|
|
36
36
|
|
|
37
|
-
You can install plugins directly from a Git repository or a
|
|
37
|
+
You can install plugins directly from a Git repository or a `.zip` file
|
|
38
|
+
instead of the marketplace — neither one goes through HMAC verification,
|
|
39
|
+
that's marketplace-only, so run `xcli plugin health` afterward (see below).
|
|
38
40
|
|
|
39
41
|
```bash title="Git Install"
|
|
40
42
|
xcli plugin install my-plugin --source git --url https://github.com/user/plugin.git
|
|
41
43
|
```
|
|
42
44
|
|
|
45
|
+
```bash title="Zip Install"
|
|
46
|
+
xcli plugin install my-plugin --source zip --url ./path/to/plugin.zip
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`--url` is required for both `git` and `zip` — omitting it fails fast
|
|
50
|
+
rather than falling back to the marketplace.
|
|
51
|
+
|
|
52
|
+
## Reinstalling
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
xcli plugin install name-of-plugin --force # overwrite an existing install
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Without `--force`, installing over an already-installed plugin (same name)
|
|
59
|
+
is refused with a one-line message telling you to add the flag.
|
|
60
|
+
|
|
61
|
+
!!! bug "`--no-deps` currently has no effect"
|
|
62
|
+
The flag is accepted (`install --no-deps`) but the installer never
|
|
63
|
+
reads it — confirmed in `xcli/plugin/install_commands.py`: dependencies
|
|
64
|
+
install the same way whether or not you pass it. Not documented as
|
|
65
|
+
working here on purpose; treat it as reserved for now.
|
|
66
|
+
|
|
43
67
|
## Management Commands
|
|
44
68
|
|
|
45
69
|
### Detailed Info
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Plugin Marketplace
|
|
2
2
|
|
|
3
|
-
Discover plugins available on the marketplace before installing them
|
|
4
|
-
|
|
3
|
+
Discover plugins available on the marketplace before installing them.
|
|
4
|
+
`browse`/`search`/`info` below are read-only and public, no credentials
|
|
5
|
+
required; `mine` is the one exception (see below).
|
|
5
6
|
|
|
6
7
|
## Browse All
|
|
7
8
|
|
|
@@ -30,6 +31,20 @@ published versions.
|
|
|
30
31
|
xcli plugin marketplace info name-of-plugin
|
|
31
32
|
```
|
|
32
33
|
|
|
34
|
+
## Your Plugins
|
|
35
|
+
|
|
36
|
+
`browse`/`search` above only ever show **public** plugins — unauthenticated
|
|
37
|
+
requests, by design. To also see your own **private** plugins, use `mine`
|
|
38
|
+
instead, which sends your API key:
|
|
39
|
+
|
|
40
|
+
```bash title="Your plugins"
|
|
41
|
+
xcli plugin marketplace mine
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Needs the same API key as installing does — see
|
|
45
|
+
[Authentication](../getting-started/auth.md). `xcli login`'s personal key
|
|
46
|
+
works here even without a project-scoped key for any specific plugin.
|
|
47
|
+
|
|
33
48
|
## What's not here
|
|
34
49
|
|
|
35
50
|
Rating a plugin (`POST /plugins/{slug}/ratings`) requires a full user
|
|
@@ -29,6 +29,35 @@ For a comprehensive check of all plugins (including manifest validation and AST
|
|
|
29
29
|
xcli plugin health
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
## Validating Manifests
|
|
33
|
+
|
|
34
|
+
Beyond signature checks, `validate` checks the plugin manifest itself
|
|
35
|
+
(`plugin.yaml` structure, required fields) and can track its **IPC
|
|
36
|
+
surface** — the actions and events a plugin exposes to others — across
|
|
37
|
+
versions:
|
|
38
|
+
|
|
39
|
+
```bash title="Validate a plugin"
|
|
40
|
+
xcli plugin security validate my-plugin
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Without a path, it scans every plugin found via `integration.yaml`
|
|
44
|
+
instead of just one:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
xcli plugin security validate
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```bash title="Track breaking IPC changes"
|
|
51
|
+
xcli plugin security validate my-plugin --save # snapshot today's IPC surface
|
|
52
|
+
xcli plugin security validate my-plugin --check-breaking # diff against that snapshot
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`--save` writes the current IPC action/event schemas to a snapshot file
|
|
56
|
+
(default `.xcore/schemas.json`, override with `--schema-file`); a later
|
|
57
|
+
`--check-breaking` run reports anything removed or changed since — useful
|
|
58
|
+
in CI to catch an accidental breaking change to a plugin's public IPC
|
|
59
|
+
contract before it ships.
|
|
60
|
+
|
|
32
61
|
## Strict Mode
|
|
33
62
|
|
|
34
63
|
Enable `strict_trusted` in your configuration to refuse any plugin that is not properly signed.
|
|
@@ -12,6 +12,15 @@ A comprehensive list of all commands available in `xcorecli`.
|
|
|
12
12
|
- `xcli services`: Show status and details of all system services (local
|
|
13
13
|
runtime — not the marketplace catalog, see `xcli service` below).
|
|
14
14
|
|
|
15
|
+
## `config` Credentials
|
|
16
|
+
|
|
17
|
+
- `config set <api-key|signing-key> <value>`: Store one credential in
|
|
18
|
+
`~/.xcli/config.json` — the manual alternative to `xcli login` above.
|
|
19
|
+
- `config show`: Print whether each credential is set (masked).
|
|
20
|
+
|
|
21
|
+
Only these two keys exist — no arbitrary `integration.yaml` settings live
|
|
22
|
+
here, see [Configuration](getting-started/configuration.md) for that file.
|
|
23
|
+
|
|
15
24
|
## `manager` Administration
|
|
16
25
|
|
|
17
26
|
- `manager start`: Start the FastAPI server (uvicorn).
|
|
@@ -50,10 +59,19 @@ A comprehensive list of all commands available in `xcorecli`.
|
|
|
50
59
|
- `marketplace browse`: List published plugins (`--sort newest|downloads|rating`).
|
|
51
60
|
- `marketplace search`: Search by name or description.
|
|
52
61
|
- `marketplace info`: Pre-install details, including published versions.
|
|
62
|
+
- `marketplace mine`: List *your* plugins — public and private alike (needs
|
|
63
|
+
an API key; `browse`/`search`/`info` above are public, no credentials).
|
|
53
64
|
|
|
54
65
|
No `rate` command — rating requires a full user session (JWT), not an API
|
|
55
66
|
key; rate plugins from the XCoreHub dashboard.
|
|
56
67
|
|
|
68
|
+
### `plugin security` Signing & validation
|
|
69
|
+
- `security sign`: HMAC-sign a plugin (`plugin.sig`).
|
|
70
|
+
- `security verify`: Verify a plugin's signature.
|
|
71
|
+
- `security validate`: Validate plugin manifest(s); `--check-breaking`
|
|
72
|
+
diffs IPC actions/events against a saved schema snapshot, `--save`
|
|
73
|
+
updates that snapshot.
|
|
74
|
+
|
|
57
75
|
### `plugin update` Maintenance
|
|
58
76
|
- `update check`: Check for new versions.
|
|
59
77
|
- `update apply`: Apply updates (--all, --dry-run).
|
|
@@ -62,7 +80,21 @@ key; rate plugins from the XCoreHub dashboard.
|
|
|
62
80
|
- `runtime load`: Activate a plugin.
|
|
63
81
|
- `runtime unload`: Deactivate a plugin.
|
|
64
82
|
- `runtime reload`: Restart a plugin.
|
|
83
|
+
- `runtime reload-all`: Restart every active plugin at once.
|
|
65
84
|
- `runtime status`: Show active plugins.
|
|
85
|
+
- `runtime call`: Invoke a plugin action directly (`--payload '{"k": "v"}'`).
|
|
86
|
+
|
|
87
|
+
## `sandbox` Isolated Execution
|
|
88
|
+
|
|
89
|
+
Run a plugin in isolation and inspect its declared resource/network/
|
|
90
|
+
filesystem policy — none of these require the plugin to already be loaded
|
|
91
|
+
by a running instance.
|
|
92
|
+
|
|
93
|
+
- `sandbox run`: Launch a plugin sandboxed, keep it running (Ctrl+C to stop).
|
|
94
|
+
- `sandbox call`: Start sandboxed, call one action, print the result, stop.
|
|
95
|
+
- `sandbox limits`: Show resource limits from the manifest.
|
|
96
|
+
- `sandbox network`: Show the declared network policy.
|
|
97
|
+
- `sandbox fs`: Show the declared filesystem policy.
|
|
66
98
|
|
|
67
99
|
## `service` Marketplace Extensions
|
|
68
100
|
|
|
@@ -104,4 +136,7 @@ confused with `xcli services`/`xcli manager services` (local runtime).
|
|
|
104
136
|
- `migration revision`: Create a new migration.
|
|
105
137
|
- `migration upgrade`: Apply migrations (--backup).
|
|
106
138
|
- `migration downgrade`: Rollback migrations.
|
|
139
|
+
- `migration current`: Show the database's current revision.
|
|
140
|
+
- `migration heads`: Show the migration chain's latest defined revision(s).
|
|
141
|
+
- `migration stamp`: Mark the DB at a revision without running scripts.
|
|
107
142
|
- `migration history`: Show migration list.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Plugin Sandboxing
|
|
2
|
+
|
|
3
|
+
The Sandbox provides a secure execution environment for third-party or untrusted plugins, ensuring they cannot compromise the host system.
|
|
4
|
+
|
|
5
|
+
## Resource Isolation
|
|
6
|
+
|
|
7
|
+
Sandboxed plugins are restricted in their resource consumption to prevent "noisy neighbor" issues or intentional Denial of Service.
|
|
8
|
+
|
|
9
|
+
### Limits Configuration
|
|
10
|
+
|
|
11
|
+
You can define default limits in `integration.yaml`:
|
|
12
|
+
|
|
13
|
+
```yaml
|
|
14
|
+
security:
|
|
15
|
+
rate_limit_default:
|
|
16
|
+
calls: 200 # Max IPC calls
|
|
17
|
+
period_seconds: 60 # Per minute
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## AST-Based Whitelisting
|
|
21
|
+
|
|
22
|
+
The core of the sandbox is an AST (Abstract Syntax Tree) analyzer that scans the plugin's code before execution.
|
|
23
|
+
|
|
24
|
+
- **Whitelisted**: Only modules listed in `security.allowed_imports` can be imported.
|
|
25
|
+
- **Blacklisted**: Modules in `security.forbidden_imports` (like `os` or `subprocess`) are explicitly blocked.
|
|
26
|
+
|
|
27
|
+
!!! danger "Sandbox Bypass"
|
|
28
|
+
Attempting to bypass the sandbox via reflection or other advanced Python techniques is monitored and will result in the plugin being immediately unloaded.
|
|
29
|
+
|
|
30
|
+
## Managing the Sandbox
|
|
31
|
+
|
|
32
|
+
Use the `sandbox` command group to run a plugin in isolation and inspect the
|
|
33
|
+
policy declared in its manifest — none of these require the plugin to
|
|
34
|
+
already be loaded by a running `xcore` instance, unlike `xcli plugin
|
|
35
|
+
runtime`.
|
|
36
|
+
|
|
37
|
+
### Run
|
|
38
|
+
|
|
39
|
+
Launch a plugin in an isolated sandbox process and keep it running until
|
|
40
|
+
you interrupt it (Ctrl+C):
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
xcli sandbox run my-sandboxed-plugin
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Call
|
|
47
|
+
|
|
48
|
+
Start the sandbox, invoke a single action, print the result, then stop —
|
|
49
|
+
useful for testing one IPC action without keeping the process alive:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
xcli sandbox call my-sandboxed-plugin send_email --payload '{"to": "user@example.com"}'
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
!!! warning "Sandboxed plugins only"
|
|
56
|
+
`sandbox call` refuses a plugin whose `execution_mode` isn't
|
|
57
|
+
`sandboxed` — use `xcli plugin runtime call` for a `trusted` plugin
|
|
58
|
+
instead.
|
|
59
|
+
|
|
60
|
+
### Limits
|
|
61
|
+
|
|
62
|
+
Show the resource limits declared in the plugin's manifest (timeout, max
|
|
63
|
+
memory/disk, rate limit) — read-only, no process started:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
xcli sandbox limits my-sandboxed-plugin
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Network
|
|
70
|
+
|
|
71
|
+
Show the plugin's declared network policy (`network:` in `plugin.yaml`):
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
xcli sandbox network my-sandboxed-plugin
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Filesystem
|
|
78
|
+
|
|
79
|
+
Show the plugin's declared filesystem policy (`filesystem:` in `plugin.yaml`):
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
xcli sandbox fs my-sandboxed-plugin
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
!!! info "Trusted vs. Sandboxed"
|
|
86
|
+
By default, plugins are treated as **Sandboxed**. You must explicitly mark a plugin as **Trusted** in its `plugin.yaml` (and usually sign it) to run it with full permissions.
|
|
@@ -17,6 +17,7 @@ from xcli.migrations.runtime import (
|
|
|
17
17
|
discover_models,
|
|
18
18
|
get_backup_dir,
|
|
19
19
|
get_database_url,
|
|
20
|
+
get_migration_dir,
|
|
20
21
|
get_scan_paths,
|
|
21
22
|
list_backups,
|
|
22
23
|
parse_db_url,
|
|
@@ -24,6 +25,7 @@ from xcli.migrations.runtime import (
|
|
|
24
25
|
render_discovery_summary,
|
|
25
26
|
restore_database,
|
|
26
27
|
run_alembic_command,
|
|
28
|
+
set_migration_dir,
|
|
27
29
|
)
|
|
28
30
|
|
|
29
31
|
_CTX = {"help_option_names": ["-h", "--help"]}
|
|
@@ -41,7 +43,7 @@ from pathlib import Path
|
|
|
41
43
|
import sys
|
|
42
44
|
|
|
43
45
|
from alembic import context
|
|
44
|
-
from sqlalchemy import engine_from_config, pool
|
|
46
|
+
from sqlalchemy import MetaData, engine_from_config, pool
|
|
45
47
|
|
|
46
48
|
config = context.config
|
|
47
49
|
ROOT = Path(config.config_file_name).resolve().parent if config.config_file_name else Path.cwd()
|
|
@@ -55,12 +57,47 @@ if config.config_file_name is not None:
|
|
|
55
57
|
|
|
56
58
|
config.set_main_option("sqlalchemy.url", get_database_url())
|
|
57
59
|
discovery = discover_models()
|
|
58
|
-
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _merge_metadata(entries: list[MetaData]) -> MetaData | None:
|
|
63
|
+
merged = MetaData()
|
|
64
|
+
seen: set[str] = set()
|
|
65
|
+
for entry in entries:
|
|
66
|
+
# `sorted_tables` respecte l'ordre des dépendances (une table est copiée après celles qu'elle référence).
|
|
67
|
+
for table in entry.sorted_tables:
|
|
68
|
+
key = table.key
|
|
69
|
+
if key in seen:
|
|
70
|
+
continue
|
|
71
|
+
seen.add(key)
|
|
72
|
+
table.to_metadata(merged)
|
|
73
|
+
# `MetaData` n'a pas de `__bool__` : un objet vide reste « vrai », c'est `tables` qu'il faut interroger.
|
|
74
|
+
return merged if merged.tables else None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# Pilotes async de l'application → pilote synchrone équivalent DÉJÀ présent dans l'environnement. `aiosqlite` n'a
|
|
78
|
+
# pas de « pilote » : le dialecte `sqlite` est déjà synchrone, d'où la chaîne vide.
|
|
79
|
+
_ASYNC_TO_SYNC = {
|
|
80
|
+
"aiosqlite": "",
|
|
81
|
+
"asyncpg": "psycopg2",
|
|
82
|
+
"aiomysql": "pymysql",
|
|
83
|
+
"asyncmy": "pymysql",
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _sync_url(url: str) -> str:
|
|
88
|
+
for async_driver, sync_driver in _ASYNC_TO_SYNC.items():
|
|
89
|
+
if f"+{async_driver}:" in url:
|
|
90
|
+
# `sqlite` n'a pas de pilote : on retire le `+` plutôt que de laisser `sqlite+:` (plugin inexistant).
|
|
91
|
+
return url.replace(f"+{async_driver}:", f"+{sync_driver}:" if sync_driver else ":", 1)
|
|
92
|
+
return url
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
target_metadata = _merge_metadata(discovery.target_metadata)
|
|
59
96
|
|
|
60
97
|
|
|
61
98
|
def run_migrations_offline() -> None:
|
|
62
99
|
context.configure(
|
|
63
|
-
url=get_database_url(),
|
|
100
|
+
url=_sync_url(get_database_url()),
|
|
64
101
|
target_metadata=target_metadata,
|
|
65
102
|
literal_binds=True,
|
|
66
103
|
dialect_opts={"paramstyle": "named"},
|
|
@@ -73,7 +110,7 @@ def run_migrations_offline() -> None:
|
|
|
73
110
|
|
|
74
111
|
def run_migrations_online() -> None:
|
|
75
112
|
configuration = config.get_section(config.config_ini_section, {})
|
|
76
|
-
configuration["sqlalchemy.url"] = get_database_url()
|
|
113
|
+
configuration["sqlalchemy.url"] = _sync_url(get_database_url())
|
|
77
114
|
connectable = engine_from_config(
|
|
78
115
|
configuration,
|
|
79
116
|
prefix="sqlalchemy.",
|
|
@@ -94,6 +131,7 @@ if context.is_offline_mode():
|
|
|
94
131
|
run_migrations_offline()
|
|
95
132
|
else:
|
|
96
133
|
run_migrations_online()
|
|
134
|
+
|
|
97
135
|
"""
|
|
98
136
|
|
|
99
137
|
_SCRIPT_TEMPLATE = '''\
|
|
@@ -165,6 +203,9 @@ datefmt = %H:%M:%S
|
|
|
165
203
|
|
|
166
204
|
# ── Helpers ────────────────────────────────────────────────────
|
|
167
205
|
|
|
206
|
+
_DIR_HELP = "Alembic directory (default: migration.directory in integration.yaml, else 'alembic')."
|
|
207
|
+
|
|
208
|
+
|
|
168
209
|
def _alembic_dir(directory: str) -> Path:
|
|
169
210
|
return (project_root() / directory).resolve()
|
|
170
211
|
|
|
@@ -199,10 +240,11 @@ def _do_backup(label: str = "") -> None:
|
|
|
199
240
|
|
|
200
241
|
@app.command("init")
|
|
201
242
|
def init(
|
|
202
|
-
directory: str = typer.Option(
|
|
243
|
+
directory: Optional[str] = typer.Option(None, "--dir", help="Alembic directory to create. " + _DIR_HELP),
|
|
203
244
|
force: bool = typer.Option(False, "--force", help="Overwrite generated files if they already exist."),
|
|
204
245
|
) -> None:
|
|
205
246
|
"""Create an Alembic workspace wired to integration.yaml and all discovered models."""
|
|
247
|
+
directory = directory or get_migration_dir()
|
|
206
248
|
root = project_root()
|
|
207
249
|
alembic_dir = _alembic_dir(directory)
|
|
208
250
|
versions_dir = alembic_dir / "versions"
|
|
@@ -234,6 +276,9 @@ def init(
|
|
|
234
276
|
encoding="utf-8",
|
|
235
277
|
)
|
|
236
278
|
|
|
279
|
+
if get_migration_dir() != directory:
|
|
280
|
+
set_migration_dir(directory)
|
|
281
|
+
|
|
237
282
|
db_info = parse_db_url(db_url)
|
|
238
283
|
|
|
239
284
|
console.print(f"[green]✓[/green] Alembic initialized → [cyan]{alembic_dir}[/cyan]")
|
|
@@ -297,9 +342,10 @@ def revision(
|
|
|
297
342
|
True, "--autogenerate/--empty",
|
|
298
343
|
help="Generate operations from discovered models.",
|
|
299
344
|
),
|
|
300
|
-
directory: str = typer.Option(
|
|
345
|
+
directory: Optional[str] = typer.Option(None, "--dir", help=_DIR_HELP),
|
|
301
346
|
) -> None:
|
|
302
347
|
"""Create a new Alembic revision from discovered models."""
|
|
348
|
+
directory = directory or get_migration_dir()
|
|
303
349
|
_ensure_initialized(directory)
|
|
304
350
|
with console.status("Scanning models..."):
|
|
305
351
|
discovery = discover_models()
|
|
@@ -310,10 +356,11 @@ def revision(
|
|
|
310
356
|
@app.command("upgrade")
|
|
311
357
|
def upgrade(
|
|
312
358
|
revision: str = typer.Argument("head", help="Target revision (head, +1, <id>)."),
|
|
313
|
-
directory: str = typer.Option(
|
|
359
|
+
directory: Optional[str] = typer.Option(None, "--dir", help=_DIR_HELP),
|
|
314
360
|
backup: bool = typer.Option(False, "--backup", "-b", help="Backup database before upgrading."),
|
|
315
361
|
) -> None:
|
|
316
362
|
"""Apply migrations up to the target revision."""
|
|
363
|
+
directory = directory or get_migration_dir()
|
|
317
364
|
_ensure_initialized(directory)
|
|
318
365
|
if backup:
|
|
319
366
|
_do_backup("pre-upgrade")
|
|
@@ -323,10 +370,11 @@ def upgrade(
|
|
|
323
370
|
@app.command("downgrade")
|
|
324
371
|
def downgrade(
|
|
325
372
|
revision: str = typer.Argument(..., help="Target revision (-1, base, <id>)."),
|
|
326
|
-
directory: str = typer.Option(
|
|
373
|
+
directory: Optional[str] = typer.Option(None, "--dir", help=_DIR_HELP),
|
|
327
374
|
backup: bool = typer.Option(True, "--backup/--no-backup", "-b", help="Backup database before downgrading."),
|
|
328
375
|
) -> None:
|
|
329
376
|
"""Rollback migrations to the target revision."""
|
|
377
|
+
directory = directory or get_migration_dir()
|
|
330
378
|
_ensure_initialized(directory)
|
|
331
379
|
if backup:
|
|
332
380
|
_do_backup("pre-downgrade")
|
|
@@ -336,9 +384,10 @@ def downgrade(
|
|
|
336
384
|
@app.command("current")
|
|
337
385
|
def current(
|
|
338
386
|
verbose: bool = typer.Option(False, "--verbose", "-v"),
|
|
339
|
-
directory: str = typer.Option(
|
|
387
|
+
directory: Optional[str] = typer.Option(None, "--dir", help=_DIR_HELP),
|
|
340
388
|
) -> None:
|
|
341
389
|
"""Show the current database revision."""
|
|
390
|
+
directory = directory or get_migration_dir()
|
|
342
391
|
_ensure_initialized(directory)
|
|
343
392
|
run_alembic_command(directory, lambda cfg: command.current(cfg, verbose=verbose))
|
|
344
393
|
|
|
@@ -346,9 +395,10 @@ def current(
|
|
|
346
395
|
@app.command("history")
|
|
347
396
|
def history(
|
|
348
397
|
verbose: bool = typer.Option(False, "--verbose", "-v"),
|
|
349
|
-
directory: str = typer.Option(
|
|
398
|
+
directory: Optional[str] = typer.Option(None, "--dir", help=_DIR_HELP),
|
|
350
399
|
) -> None:
|
|
351
400
|
"""Show migration history."""
|
|
401
|
+
directory = directory or get_migration_dir()
|
|
352
402
|
_ensure_initialized(directory)
|
|
353
403
|
command.history(create_alembic_config(directory), verbose=verbose)
|
|
354
404
|
|
|
@@ -356,9 +406,10 @@ def history(
|
|
|
356
406
|
@app.command("heads")
|
|
357
407
|
def heads(
|
|
358
408
|
verbose: bool = typer.Option(False, "--verbose", "-v"),
|
|
359
|
-
directory: str = typer.Option(
|
|
409
|
+
directory: Optional[str] = typer.Option(None, "--dir", help=_DIR_HELP),
|
|
360
410
|
) -> None:
|
|
361
411
|
"""Show migration heads."""
|
|
412
|
+
directory = directory or get_migration_dir()
|
|
362
413
|
_ensure_initialized(directory)
|
|
363
414
|
command.heads(create_alembic_config(directory), verbose=verbose)
|
|
364
415
|
|
|
@@ -366,13 +417,55 @@ def heads(
|
|
|
366
417
|
@app.command("stamp")
|
|
367
418
|
def stamp(
|
|
368
419
|
revision: str = typer.Argument(..., help="Revision to stamp without running migrations."),
|
|
369
|
-
directory: str = typer.Option(
|
|
420
|
+
directory: Optional[str] = typer.Option(None, "--dir", help=_DIR_HELP),
|
|
370
421
|
) -> None:
|
|
371
422
|
"""Stamp the database at a revision without running migrations."""
|
|
423
|
+
directory = directory or get_migration_dir()
|
|
372
424
|
_ensure_initialized(directory)
|
|
373
425
|
run_alembic_command(directory, lambda cfg: command.stamp(cfg, revision))
|
|
374
426
|
|
|
375
427
|
|
|
428
|
+
@app.command("rename")
|
|
429
|
+
def rename_dir(
|
|
430
|
+
new_name: str = typer.Argument(..., help="New name for the Alembic directory."),
|
|
431
|
+
directory: Optional[str] = typer.Option(None, "--dir", help="Current directory name. " + _DIR_HELP),
|
|
432
|
+
) -> None:
|
|
433
|
+
"""Rename the Alembic directory and keep alembic.ini / integration.yaml in sync."""
|
|
434
|
+
directory = directory or get_migration_dir()
|
|
435
|
+
_ensure_initialized(directory)
|
|
436
|
+
|
|
437
|
+
root = project_root()
|
|
438
|
+
old_dir = _alembic_dir(directory)
|
|
439
|
+
new_dir = (root / new_name).resolve()
|
|
440
|
+
|
|
441
|
+
if new_dir.exists():
|
|
442
|
+
console.print(f"[red]Already exists:[/red] [cyan]{new_dir}[/cyan]")
|
|
443
|
+
raise typer.Exit(1)
|
|
444
|
+
|
|
445
|
+
old_dir.rename(new_dir)
|
|
446
|
+
|
|
447
|
+
ini_path = root / "alembic.ini"
|
|
448
|
+
ini_updated = False
|
|
449
|
+
if ini_path.exists():
|
|
450
|
+
text = ini_path.read_text(encoding="utf-8")
|
|
451
|
+
new_text = text.replace(f"script_location = {directory}", f"script_location = {new_name}", 1)
|
|
452
|
+
if new_text != text:
|
|
453
|
+
ini_path.write_text(new_text, encoding="utf-8")
|
|
454
|
+
ini_updated = True
|
|
455
|
+
|
|
456
|
+
set_migration_dir(new_name)
|
|
457
|
+
|
|
458
|
+
console.print(f"[green]✓[/green] Renamed [cyan]{directory}[/cyan] → [cyan]{new_name}[/cyan]")
|
|
459
|
+
if ini_updated:
|
|
460
|
+
console.print(f"[green]✓[/green] Updated [cyan]{ini_path}[/cyan]")
|
|
461
|
+
elif ini_path.exists():
|
|
462
|
+
console.print(
|
|
463
|
+
f"[yellow]⚠[/yellow] Could not find [dim]script_location = {directory}[/dim] in "
|
|
464
|
+
f"[cyan]{ini_path}[/cyan] — update it by hand."
|
|
465
|
+
)
|
|
466
|
+
console.print(f"[green]✓[/green] integration.yaml → [cyan]migration.directory: {new_name}[/cyan]")
|
|
467
|
+
|
|
468
|
+
|
|
376
469
|
# ── Backup / Restore ───────────────────────────────────────────
|
|
377
470
|
|
|
378
471
|
@app.command("backup")
|
|
@@ -202,9 +202,38 @@ def get_backup_dir() -> Path:
|
|
|
202
202
|
return p.resolve()
|
|
203
203
|
|
|
204
204
|
|
|
205
|
-
def
|
|
205
|
+
def get_migration_dir() -> str:
|
|
206
|
+
"""Name of the Alembic directory, persisted once by `xcli migration init`
|
|
207
|
+
(or `rename`) so every other command agrees on it without `--dir` having
|
|
208
|
+
to be repeated on each invocation."""
|
|
209
|
+
cfg = load_config()
|
|
210
|
+
return cfg.get("migration", {}).get("directory", "alembic")
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def set_migration_dir(name: str) -> None:
|
|
214
|
+
"""Persist `migration.directory` in integration.yaml/json, backing up the
|
|
215
|
+
previous file first — mirrors the backup-then-rewrite convention used by
|
|
216
|
+
`xcli init upgrade` (xcli/init/upgrade.py)."""
|
|
217
|
+
path = require_config_path()
|
|
218
|
+
cfg = load_config()
|
|
219
|
+
cfg.setdefault("migration", {})["directory"] = name
|
|
220
|
+
|
|
221
|
+
bak = path.with_name(path.name + ".bak")
|
|
222
|
+
bak.write_text(path.read_text(encoding="utf-8"), encoding="utf-8")
|
|
223
|
+
|
|
224
|
+
if path.suffix.lower() == ".json":
|
|
225
|
+
import json
|
|
226
|
+
path.write_text(json.dumps(cfg, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
227
|
+
else:
|
|
228
|
+
path.write_text(
|
|
229
|
+
yaml.dump(cfg, default_flow_style=False, allow_unicode=True, sort_keys=False),
|
|
230
|
+
encoding="utf-8",
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def create_alembic_config(directory: str | None = None) -> Config:
|
|
206
235
|
root = project_root()
|
|
207
|
-
alembic_dir = root / directory
|
|
236
|
+
alembic_dir = root / (directory or get_migration_dir())
|
|
208
237
|
cfg = Config()
|
|
209
238
|
cfg.set_main_option("script_location", str(alembic_dir))
|
|
210
239
|
cfg.set_main_option("sqlalchemy.url", get_database_url())
|
|
@@ -533,8 +562,13 @@ def list_backups(backup_dir: Path | None = None) -> list[Path]:
|
|
|
533
562
|
|
|
534
563
|
|
|
535
564
|
def _iter_python_files(scan_root: Path) -> Iterator[Path]:
|
|
565
|
+
# `_IGNORED_PARTS` only covers the literal defaults ("alembic", "migrations") —
|
|
566
|
+
# a project that renamed its migration directory (migration.directory /
|
|
567
|
+
# `xcli migration rename`) needs that name excluded too, or model discovery
|
|
568
|
+
# starts importing migration scripts as if they were app modules.
|
|
569
|
+
ignored = _IGNORED_PARTS | {get_migration_dir()}
|
|
536
570
|
for path in scan_root.rglob("*.py"):
|
|
537
|
-
if any(part in
|
|
571
|
+
if any(part in ignored for part in path.parts):
|
|
538
572
|
continue
|
|
539
573
|
yield path
|
|
540
574
|
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
# CLI Configuration
|
|
2
|
-
|
|
3
|
-
The `config` command group allows you to manage the behavior of `xcorecli` itself and its interaction with the `xcore` project.
|
|
4
|
-
|
|
5
|
-
## Commands Overview
|
|
6
|
-
|
|
7
|
-
| Command | Description |
|
|
8
|
-
|---------|-------------|
|
|
9
|
-
| `show` | Display the current merged configuration. |
|
|
10
|
-
| `get` | Retrieve a specific configuration value. |
|
|
11
|
-
| `set` | Update a configuration value. |
|
|
12
|
-
| `validate` | Check `integration.yaml` for schema compliance. |
|
|
13
|
-
|
|
14
|
-
## Managing Settings
|
|
15
|
-
|
|
16
|
-
### Viewing Configuration
|
|
17
|
-
|
|
18
|
-
To see your current setup, including defaults and overrides from `integration.yaml`:
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
xcli config show
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
### Updating Values
|
|
25
|
-
|
|
26
|
-
You can modify settings directly from the CLI. These changes are typically applied to your local configuration or the project's `integration.yaml`.
|
|
27
|
-
|
|
28
|
-
```bash title="Set Value"
|
|
29
|
-
xcli config set app.debug true
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
!!! note "Layered Configuration"
|
|
33
|
-
`xcorecli` uses a layered approach:
|
|
34
|
-
1. Internal Defaults
|
|
35
|
-
2. `integration.yaml` (Project level)
|
|
36
|
-
3. Local CLI Config (User level)
|
|
37
|
-
4. Environment Variables
|
|
38
|
-
|
|
39
|
-
## Runtime Configuration
|
|
40
|
-
|
|
41
|
-
Some settings in the `runtime` section of `integration.yaml` can be adjusted without restarting the entire system, such as plugin reload intervals.
|
|
42
|
-
|
|
43
|
-
```yaml
|
|
44
|
-
plugins:
|
|
45
|
-
interval: 5 # Check for changes every 5 seconds
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
!!! info "Dynamic Updates"
|
|
49
|
-
Use `xcli manager services reload` to apply certain configuration changes on the fly.
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# Plugin Sandboxing
|
|
2
|
-
|
|
3
|
-
The Sandbox provides a secure execution environment for third-party or untrusted plugins, ensuring they cannot compromise the host system.
|
|
4
|
-
|
|
5
|
-
## Resource Isolation
|
|
6
|
-
|
|
7
|
-
Sandboxed plugins are restricted in their resource consumption to prevent "noisy neighbor" issues or intentional Denial of Service.
|
|
8
|
-
|
|
9
|
-
### Limits Configuration
|
|
10
|
-
|
|
11
|
-
You can define default limits in `integration.yaml`:
|
|
12
|
-
|
|
13
|
-
```yaml
|
|
14
|
-
security:
|
|
15
|
-
rate_limit_default:
|
|
16
|
-
calls: 200 # Max IPC calls
|
|
17
|
-
period_seconds: 60 # Per minute
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## AST-Based Whitelisting
|
|
21
|
-
|
|
22
|
-
The core of the sandbox is an AST (Abstract Syntax Tree) analyzer that scans the plugin's code before execution.
|
|
23
|
-
|
|
24
|
-
- **Whitelisted**: Only modules listed in `security.allowed_imports` can be imported.
|
|
25
|
-
- **Blacklisted**: Modules in `security.forbidden_imports` (like `os` or `subprocess`) are explicitly blocked.
|
|
26
|
-
|
|
27
|
-
!!! danger "Sandbox Bypass"
|
|
28
|
-
Attempting to bypass the sandbox via reflection or other advanced Python techniques is monitored and will result in the plugin being immediately unloaded.
|
|
29
|
-
|
|
30
|
-
## Managing the Sandbox
|
|
31
|
-
|
|
32
|
-
Use the `sandbox` command group to inspect the status of the isolation layer.
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
# View sandbox statistics
|
|
36
|
-
xcli sandbox stats
|
|
37
|
-
|
|
38
|
-
# Inspect a specific plugin's sandbox
|
|
39
|
-
xcli sandbox inspect my-sandboxed-plugin
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
!!! info "Trusted vs. Sandboxed"
|
|
43
|
-
By default, plugins are treated as **Sandboxed**. You must explicitly mark a plugin as **Trusted** in its `plugin.yaml` (and usually sign it) to run it with full permissions.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|