stml-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.
- stml_cli-0.1.0/PKG-INFO +86 -0
- stml_cli-0.1.0/README.md +76 -0
- stml_cli-0.1.0/pyproject.toml +24 -0
- stml_cli-0.1.0/setup.cfg +4 -0
- stml_cli-0.1.0/stml_cli/__init__.py +22 -0
- stml_cli-0.1.0/stml_cli/__main__.py +153 -0
- stml_cli-0.1.0/stml_cli/commands.py +322 -0
- stml_cli-0.1.0/stml_cli/config.py +64 -0
- stml_cli-0.1.0/stml_cli/http.py +164 -0
- stml_cli-0.1.0/stml_cli/oauth.py +90 -0
- stml_cli-0.1.0/stml_cli/packaging.py +42 -0
- stml_cli-0.1.0/stml_cli/paths.py +70 -0
- stml_cli-0.1.0/stml_cli/refs.py +47 -0
- stml_cli-0.1.0/stml_cli.egg-info/PKG-INFO +86 -0
- stml_cli-0.1.0/stml_cli.egg-info/SOURCES.txt +21 -0
- stml_cli-0.1.0/stml_cli.egg-info/dependency_links.txt +1 -0
- stml_cli-0.1.0/stml_cli.egg-info/entry_points.txt +2 -0
- stml_cli-0.1.0/stml_cli.egg-info/requires.txt +1 -0
- stml_cli-0.1.0/stml_cli.egg-info/top_level.txt +1 -0
- stml_cli-0.1.0/tests/test_disambiguation.py +36 -0
- stml_cli-0.1.0/tests/test_paths.py +65 -0
- stml_cli-0.1.0/tests/test_refs.py +51 -0
- stml_cli-0.1.0/tests/test_whoami_expiry.py +33 -0
stml_cli-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: stml-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The stml partner CLI — pull, edit, push, and publish stml apps and libraries.
|
|
5
|
+
Author: stml
|
|
6
|
+
Project-URL: Homepage, https://stml.io
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: certifi
|
|
10
|
+
|
|
11
|
+
# stml — the partner CLI
|
|
12
|
+
|
|
13
|
+
Pull an app you built on the platform down to a local folder, edit it with your
|
|
14
|
+
own editor and git, push it back to test, and publish a finished version to the
|
|
15
|
+
App Store — without cloning the stml4 monorepo.
|
|
16
|
+
|
|
17
|
+
Spec: [`docs/user-stories/feature/024-app-authoring-platform.md`](../docs/user-stories/feature/024-app-authoring-platform.md).
|
|
18
|
+
|
|
19
|
+
## Requirements
|
|
20
|
+
|
|
21
|
+
- **Python 3.11 or newer** on your PATH. That's the only hard requirement.
|
|
22
|
+
- macOS: `brew install python@3.12` (or python.org). **Not** the system
|
|
23
|
+
`/usr/bin/python3` — it's often 3.9, which is too old.
|
|
24
|
+
- Linux: your distro's Python 3.11+, or `pipx`/`pyenv`/`uv`.
|
|
25
|
+
- Windows 10/11: the python.org installer (gives you the `py` launcher).
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
| Method | Command |
|
|
30
|
+
|---|---|
|
|
31
|
+
| **pipx** (recommended) | `pipx install stml-cli` |
|
|
32
|
+
| pip (in a venv) | `pip install stml-cli` |
|
|
33
|
+
| single file (no installer) | `curl -fsSL <backend>/cli/stml -o stml && chmod +x stml` |
|
|
34
|
+
|
|
35
|
+
Runs on **macOS, Linux, and Windows** — it's pure-Python and touches nothing
|
|
36
|
+
platform-specific beyond the filesystem and HTTPS.
|
|
37
|
+
|
|
38
|
+
## Quickstart
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
# Point at your backend (default: http://localhost:8010)
|
|
42
|
+
export STML4_BACKEND=https://api.stml.io
|
|
43
|
+
|
|
44
|
+
# 1. Sign in — opens your browser; approve, and a session is cached locally.
|
|
45
|
+
stml login
|
|
46
|
+
stml whoami
|
|
47
|
+
|
|
48
|
+
# 2. See your apps and their URLs (the handle you pull/push by).
|
|
49
|
+
stml list
|
|
50
|
+
|
|
51
|
+
# 3. Pull an app's source into a folder — identify it by its URL (copy from
|
|
52
|
+
# `stml list` or the browser; a trailing /flows/… or /sessions/… is fine).
|
|
53
|
+
stml pull https://app.stml.io/orgs/acme/ws/main/apps/my-app-a1b2 ./my-app
|
|
54
|
+
|
|
55
|
+
# 3. Edit with your own tools, commit to git, whatever.
|
|
56
|
+
|
|
57
|
+
# 4. Push it back — checks, writes the overlay, and deploys in one step.
|
|
58
|
+
stml push ./my-app # app remembered from pull
|
|
59
|
+
# or: stml push https://app.stml.io/orgs/acme/ws/main/apps/my-app-a1b2 ./my-app
|
|
60
|
+
|
|
61
|
+
# 5. When ready, publish a version to the App Store.
|
|
62
|
+
# (bump project.version in pyproject.toml first; set [tool.stml].issuer-org)
|
|
63
|
+
stml publish ./my-app
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Authentication
|
|
67
|
+
|
|
68
|
+
`stml login` runs the platform's browser OAuth flow (the same one the monorepo's
|
|
69
|
+
`npm run libs:login` uses): you authenticate in the browser, approve, and a
|
|
70
|
+
**refresh token** is cached under your OS config dir (`~/Library/Application
|
|
71
|
+
Support/stml`, `$XDG_CONFIG_HOME/stml`, or `%APPDATA%\stml`). Access tokens are
|
|
72
|
+
minted per-command and never written to disk.
|
|
73
|
+
|
|
74
|
+
`STML4_TOKEN`, if set, overrides the cache verbatim — how CI and the monorepo
|
|
75
|
+
(`STML4_TOKEN=$(npm run --silent token)`) authenticate.
|
|
76
|
+
|
|
77
|
+
## Commands
|
|
78
|
+
|
|
79
|
+
| Command | What |
|
|
80
|
+
|---|---|
|
|
81
|
+
| `stml login` / `logout` / `whoami` | session auth |
|
|
82
|
+
| `stml list [--workspace <ws>]` | list your apps with their app URLs |
|
|
83
|
+
| `stml pull <app-url> [<dir>]` | pull overlay source to a folder (bare slug also works) |
|
|
84
|
+
| `stml push [<app-url>] <dir>` | push a folder to the overlay **and deploy** (`--no-deploy`, `--prune`) |
|
|
85
|
+
| `stml publish <dir> [--org <slug>]` | publish a library version from a folder |
|
|
86
|
+
| `stml init <dir> [--name <name>]` | scaffold a new library directory |
|
stml_cli-0.1.0/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# stml — the partner CLI
|
|
2
|
+
|
|
3
|
+
Pull an app you built on the platform down to a local folder, edit it with your
|
|
4
|
+
own editor and git, push it back to test, and publish a finished version to the
|
|
5
|
+
App Store — without cloning the stml4 monorepo.
|
|
6
|
+
|
|
7
|
+
Spec: [`docs/user-stories/feature/024-app-authoring-platform.md`](../docs/user-stories/feature/024-app-authoring-platform.md).
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- **Python 3.11 or newer** on your PATH. That's the only hard requirement.
|
|
12
|
+
- macOS: `brew install python@3.12` (or python.org). **Not** the system
|
|
13
|
+
`/usr/bin/python3` — it's often 3.9, which is too old.
|
|
14
|
+
- Linux: your distro's Python 3.11+, or `pipx`/`pyenv`/`uv`.
|
|
15
|
+
- Windows 10/11: the python.org installer (gives you the `py` launcher).
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
| Method | Command |
|
|
20
|
+
|---|---|
|
|
21
|
+
| **pipx** (recommended) | `pipx install stml-cli` |
|
|
22
|
+
| pip (in a venv) | `pip install stml-cli` |
|
|
23
|
+
| single file (no installer) | `curl -fsSL <backend>/cli/stml -o stml && chmod +x stml` |
|
|
24
|
+
|
|
25
|
+
Runs on **macOS, Linux, and Windows** — it's pure-Python and touches nothing
|
|
26
|
+
platform-specific beyond the filesystem and HTTPS.
|
|
27
|
+
|
|
28
|
+
## Quickstart
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
# Point at your backend (default: http://localhost:8010)
|
|
32
|
+
export STML4_BACKEND=https://api.stml.io
|
|
33
|
+
|
|
34
|
+
# 1. Sign in — opens your browser; approve, and a session is cached locally.
|
|
35
|
+
stml login
|
|
36
|
+
stml whoami
|
|
37
|
+
|
|
38
|
+
# 2. See your apps and their URLs (the handle you pull/push by).
|
|
39
|
+
stml list
|
|
40
|
+
|
|
41
|
+
# 3. Pull an app's source into a folder — identify it by its URL (copy from
|
|
42
|
+
# `stml list` or the browser; a trailing /flows/… or /sessions/… is fine).
|
|
43
|
+
stml pull https://app.stml.io/orgs/acme/ws/main/apps/my-app-a1b2 ./my-app
|
|
44
|
+
|
|
45
|
+
# 3. Edit with your own tools, commit to git, whatever.
|
|
46
|
+
|
|
47
|
+
# 4. Push it back — checks, writes the overlay, and deploys in one step.
|
|
48
|
+
stml push ./my-app # app remembered from pull
|
|
49
|
+
# or: stml push https://app.stml.io/orgs/acme/ws/main/apps/my-app-a1b2 ./my-app
|
|
50
|
+
|
|
51
|
+
# 5. When ready, publish a version to the App Store.
|
|
52
|
+
# (bump project.version in pyproject.toml first; set [tool.stml].issuer-org)
|
|
53
|
+
stml publish ./my-app
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Authentication
|
|
57
|
+
|
|
58
|
+
`stml login` runs the platform's browser OAuth flow (the same one the monorepo's
|
|
59
|
+
`npm run libs:login` uses): you authenticate in the browser, approve, and a
|
|
60
|
+
**refresh token** is cached under your OS config dir (`~/Library/Application
|
|
61
|
+
Support/stml`, `$XDG_CONFIG_HOME/stml`, or `%APPDATA%\stml`). Access tokens are
|
|
62
|
+
minted per-command and never written to disk.
|
|
63
|
+
|
|
64
|
+
`STML4_TOKEN`, if set, overrides the cache verbatim — how CI and the monorepo
|
|
65
|
+
(`STML4_TOKEN=$(npm run --silent token)`) authenticate.
|
|
66
|
+
|
|
67
|
+
## Commands
|
|
68
|
+
|
|
69
|
+
| Command | What |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `stml login` / `logout` / `whoami` | session auth |
|
|
72
|
+
| `stml list [--workspace <ws>]` | list your apps with their app URLs |
|
|
73
|
+
| `stml pull <app-url> [<dir>]` | pull overlay source to a folder (bare slug also works) |
|
|
74
|
+
| `stml push [<app-url>] <dir>` | push a folder to the overlay **and deploy** (`--no-deploy`, `--prune`) |
|
|
75
|
+
| `stml publish <dir> [--org <slug>]` | publish a library version from a folder |
|
|
76
|
+
| `stml init <dir> [--name <name>]` | scaffold a new library directory |
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "stml-cli"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "The stml partner CLI — pull, edit, push, and publish stml apps and libraries."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
authors = [{ name = "stml" }]
|
|
8
|
+
# Runtime is stdlib-only; certifi is the sole dependency, for TLS trust on
|
|
9
|
+
# framework Python builds that ship no CA bundle (024 §Distributing the partner CLI).
|
|
10
|
+
dependencies = ["certifi"]
|
|
11
|
+
|
|
12
|
+
[project.scripts]
|
|
13
|
+
stml = "stml_cli.__main__:main"
|
|
14
|
+
|
|
15
|
+
[project.urls]
|
|
16
|
+
Homepage = "https://stml.io"
|
|
17
|
+
|
|
18
|
+
[build-system]
|
|
19
|
+
requires = ["setuptools>=68"]
|
|
20
|
+
build-backend = "setuptools.build_meta"
|
|
21
|
+
|
|
22
|
+
[tool.setuptools.packages.find]
|
|
23
|
+
where = ["."]
|
|
24
|
+
include = ["stml_cli*"]
|
stml_cli-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""stml — the partner CLI (024 §Distributing the partner CLI).
|
|
2
|
+
|
|
3
|
+
Pure-Python, stdlib only. ``certifi`` is the sole packaging dependency, used to
|
|
4
|
+
point OpenSSL at a CA bundle so HTTPS to a real backend verifies on framework
|
|
5
|
+
Python builds that ship no CA store (same fix as the monorepo publish scripts).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
|
|
12
|
+
__version__ = "0.1.0"
|
|
13
|
+
|
|
14
|
+
# TLS trust: framework/python.org builds have no OpenSSL CA path, so a real
|
|
15
|
+
# https backend fails with "unable to get local issuer certificate". certifi's
|
|
16
|
+
# bundle fixes it. setdefault honours an operator override; no-op for http.
|
|
17
|
+
try: # pragma: no cover - environment dependent
|
|
18
|
+
import certifi
|
|
19
|
+
|
|
20
|
+
os.environ.setdefault("SSL_CERT_FILE", certifi.where())
|
|
21
|
+
except ImportError: # pragma: no cover
|
|
22
|
+
pass
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""`stml` CLI entry point — argparse dispatch (024 §CLI surface).
|
|
2
|
+
|
|
3
|
+
stml login | logout | whoami
|
|
4
|
+
stml list [--workspace <ws>]
|
|
5
|
+
stml pull <app-url> [<dir>]
|
|
6
|
+
stml push [<app-url>] <dir>
|
|
7
|
+
stml detach <app-url> (fork: own the full source)
|
|
8
|
+
stml revert <app-url> [--yes] (reset: back to the clean library)
|
|
9
|
+
stml status <app-url>
|
|
10
|
+
stml publish <dir> [--org <slug>]
|
|
11
|
+
stml init <dir> [--name <name>]
|
|
12
|
+
|
|
13
|
+
Auth: STML4_TOKEN overrides everything; otherwise the cached `stml login`
|
|
14
|
+
session is used (and silently refreshed). Backend: --backend or $STML4_BACKEND.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import sys
|
|
21
|
+
|
|
22
|
+
from . import __version__, commands, http
|
|
23
|
+
from .config import DEFAULT_BACKEND
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
27
|
+
p = argparse.ArgumentParser(prog="stml", description="The stml partner CLI.")
|
|
28
|
+
p.add_argument("--version", action="version", version=f"stml {__version__}")
|
|
29
|
+
p.add_argument("--backend", default=DEFAULT_BACKEND, help=f"Backend URL (default: {DEFAULT_BACKEND})")
|
|
30
|
+
sub = p.add_subparsers(dest="cmd", required=True)
|
|
31
|
+
|
|
32
|
+
sub.add_parser("login", help="Authenticate in the browser and cache a session").set_defaults(func=commands.cmd_login)
|
|
33
|
+
sub.add_parser("logout", help="Delete the cached session").set_defaults(func=commands.cmd_logout)
|
|
34
|
+
sub.add_parser("whoami", help="Show the resolved user + backend").set_defaults(func=commands.cmd_whoami)
|
|
35
|
+
|
|
36
|
+
ls = sub.add_parser(
|
|
37
|
+
"list",
|
|
38
|
+
help="List apps you can access (name + pull handle)",
|
|
39
|
+
description="List apps you can access, with the exact `stml pull` handle for each.",
|
|
40
|
+
)
|
|
41
|
+
ls.add_argument("--workspace", metavar="<workspace>", help="Filter to one workspace (slug or name)")
|
|
42
|
+
ls.set_defaults(func=commands.cmd_list)
|
|
43
|
+
|
|
44
|
+
# pull / push (an app's source)
|
|
45
|
+
pull = sub.add_parser(
|
|
46
|
+
"pull",
|
|
47
|
+
help="Pull an app's source to a local folder",
|
|
48
|
+
description="Pull an app's source to a local folder.",
|
|
49
|
+
epilog=(
|
|
50
|
+
"Identify the app by its URL — copy it from `stml list` or straight from\n"
|
|
51
|
+
"the browser. A trailing /flows/…, /sessions/… or /settings is ignored, so\n"
|
|
52
|
+
"the URL of a flow or a run resolves to its app just fine.\n\n"
|
|
53
|
+
"examples:\n"
|
|
54
|
+
" stml pull https://app.stml.io/orgs/acme/ws/main/apps/invoice-sync-a1b2\n"
|
|
55
|
+
" stml pull https://app.stml.io/orgs/acme/ws/main/apps/invoice-sync-a1b2/flows/x ./work\n"
|
|
56
|
+
" stml pull invoice-sync-a1b2 # a bare slug also works"
|
|
57
|
+
),
|
|
58
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
59
|
+
)
|
|
60
|
+
pull.add_argument("app", metavar="<app-url>", help="App URL (from `stml list` or the browser); a bare slug also works")
|
|
61
|
+
pull.add_argument("dir", nargs="?", metavar="<dir>", help="Target folder (default: ./<app-slug>)")
|
|
62
|
+
pull.set_defaults(func=commands.cmd_pull)
|
|
63
|
+
|
|
64
|
+
push = sub.add_parser(
|
|
65
|
+
"push",
|
|
66
|
+
help="Push a local folder into the app overlay and deploy",
|
|
67
|
+
description="Push a local folder into the app's overlay and deploy it.",
|
|
68
|
+
epilog=(
|
|
69
|
+
"The <app-url> is optional when pushing a folder created by `stml pull`\n"
|
|
70
|
+
"(it remembers the app in .stml/app.json).\n\n"
|
|
71
|
+
"examples:\n"
|
|
72
|
+
" stml push ./invoice-sync # remembered from pull\n"
|
|
73
|
+
" stml push https://app.stml.io/orgs/acme/ws/main/apps/invoice-sync-a1b2 ./invoice-sync\n"
|
|
74
|
+
" stml push ./invoice-sync --no-deploy"
|
|
75
|
+
),
|
|
76
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
77
|
+
)
|
|
78
|
+
push.add_argument("app", nargs="?", metavar="<app-url>", help="App URL to push to (optional for a pulled folder)")
|
|
79
|
+
push.add_argument("dir", metavar="<dir>", help="Source folder to push")
|
|
80
|
+
push.add_argument("--no-deploy", action="store_true", help="Write the overlay without deploying")
|
|
81
|
+
push.add_argument("--prune", action="store_true", help="Delete overlay paths absent from the folder")
|
|
82
|
+
push.set_defaults(func=commands.cmd_push)
|
|
83
|
+
|
|
84
|
+
# detach / revert / status (story 035 — the app's attachment lifecycle)
|
|
85
|
+
detach = sub.add_parser(
|
|
86
|
+
"detach",
|
|
87
|
+
help="Fork an installed app: full source into the overlay, library pin cleared",
|
|
88
|
+
description=(
|
|
89
|
+
"Fork an installed app into a self-contained one. The full effective source "
|
|
90
|
+
"(base library + your overlay) is materialised into the app's overlay and the "
|
|
91
|
+
"library pin is cleared — after this, `stml pull` returns the whole tree and "
|
|
92
|
+
"`stml publish` can ship it as your own standalone library. The app stops "
|
|
93
|
+
"receiving library updates. Non-destructive; `stml revert` undoes it."
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
detach.add_argument("app", metavar="<app-url>", help="App URL (from `stml list`); a bare slug also works")
|
|
97
|
+
detach.set_defaults(func=commands.cmd_detach)
|
|
98
|
+
|
|
99
|
+
revert = sub.add_parser(
|
|
100
|
+
"revert",
|
|
101
|
+
help="DESTRUCTIVE: discard all customisations, restore the clean library version",
|
|
102
|
+
description=(
|
|
103
|
+
"Permanently delete every overlay customisation (code AND page edits) and restore "
|
|
104
|
+
"the app to its clean library version. A detached app is re-attached to the exact "
|
|
105
|
+
"version it was forked from. Connections, configurations, triggers and run history "
|
|
106
|
+
"are preserved. THERE IS NO UNDO."
|
|
107
|
+
),
|
|
108
|
+
)
|
|
109
|
+
revert.add_argument("app", metavar="<app-url>", help="App URL (from `stml list`); a bare slug also works")
|
|
110
|
+
revert.add_argument("--yes", action="store_true", help="Skip the interactive confirmation")
|
|
111
|
+
revert.set_defaults(func=commands.cmd_revert)
|
|
112
|
+
|
|
113
|
+
status = sub.add_parser(
|
|
114
|
+
"status",
|
|
115
|
+
help="Show an app's mode (attached / detached / scratch) and provenance",
|
|
116
|
+
)
|
|
117
|
+
status.add_argument("app", metavar="<app-url>", help="App URL (from `stml list`); a bare slug also works")
|
|
118
|
+
status.set_defaults(func=commands.cmd_status)
|
|
119
|
+
|
|
120
|
+
# publish / init (library)
|
|
121
|
+
publish = sub.add_parser("publish", help="Publish a library version from a local folder")
|
|
122
|
+
publish.add_argument("dir")
|
|
123
|
+
publish.add_argument("--org", help="Issuer org slug (overrides [tool.stml].issuer-org)")
|
|
124
|
+
publish.set_defaults(func=commands.cmd_publish)
|
|
125
|
+
|
|
126
|
+
init = sub.add_parser("init", help="Scaffold a new library directory")
|
|
127
|
+
init.add_argument("dir")
|
|
128
|
+
init.add_argument("--name")
|
|
129
|
+
init.set_defaults(func=commands.cmd_init)
|
|
130
|
+
|
|
131
|
+
return p
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def main(argv: list[str] | None = None) -> int:
|
|
135
|
+
args = build_parser().parse_args(argv)
|
|
136
|
+
try:
|
|
137
|
+
args.func(args)
|
|
138
|
+
except http.HttpError as e:
|
|
139
|
+
# An unhandled server/transport error (handled ones — e.g. a 409 the
|
|
140
|
+
# pull picker catches — never reach here). Print the clean detail.
|
|
141
|
+
print(f"✗ {e.detail}", file=sys.stderr)
|
|
142
|
+
return 1
|
|
143
|
+
except SystemExit as e:
|
|
144
|
+
# SystemExit carries our user-facing error strings; print and exit non-zero.
|
|
145
|
+
if isinstance(e.code, str):
|
|
146
|
+
print(e.code, file=sys.stderr)
|
|
147
|
+
return 1
|
|
148
|
+
return e.code or 0
|
|
149
|
+
return 0
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
if __name__ == "__main__":
|
|
153
|
+
raise SystemExit(main())
|