rathflow-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.
Files changed (36) hide show
  1. rathflow_cli-0.1.0/LICENSE +21 -0
  2. rathflow_cli-0.1.0/PKG-INFO +164 -0
  3. rathflow_cli-0.1.0/README.md +133 -0
  4. rathflow_cli-0.1.0/pyproject.toml +47 -0
  5. rathflow_cli-0.1.0/rathflow_cli/__init__.py +6 -0
  6. rathflow_cli-0.1.0/rathflow_cli/__main__.py +6 -0
  7. rathflow_cli-0.1.0/rathflow_cli/cli.py +161 -0
  8. rathflow_cli-0.1.0/rathflow_cli/commands/__init__.py +0 -0
  9. rathflow_cli-0.1.0/rathflow_cli/commands/_common.py +78 -0
  10. rathflow_cli-0.1.0/rathflow_cli/commands/admin.py +83 -0
  11. rathflow_cli-0.1.0/rathflow_cli/commands/agent.py +288 -0
  12. rathflow_cli-0.1.0/rathflow_cli/commands/api_cmd.py +75 -0
  13. rathflow_cli-0.1.0/rathflow_cli/commands/asset.py +449 -0
  14. rathflow_cli-0.1.0/rathflow_cli/commands/auth_cmd.py +160 -0
  15. rathflow_cli-0.1.0/rathflow_cli/commands/billing.py +71 -0
  16. rathflow_cli-0.1.0/rathflow_cli/commands/config_cmd.py +98 -0
  17. rathflow_cli-0.1.0/rathflow_cli/commands/memory.py +275 -0
  18. rathflow_cli-0.1.0/rathflow_cli/commands/org.py +70 -0
  19. rathflow_cli-0.1.0/rathflow_cli/commands/project.py +223 -0
  20. rathflow_cli-0.1.0/rathflow_cli/commands/sandbox.py +282 -0
  21. rathflow_cli-0.1.0/rathflow_cli/commands/session.py +237 -0
  22. rathflow_cli-0.1.0/rathflow_cli/commands/workflow.py +81 -0
  23. rathflow_cli-0.1.0/rathflow_cli/config.py +69 -0
  24. rathflow_cli-0.1.0/rathflow_cli/endpoints.py +207 -0
  25. rathflow_cli-0.1.0/rathflow_cli/errors.py +70 -0
  26. rathflow_cli-0.1.0/rathflow_cli/http.py +294 -0
  27. rathflow_cli-0.1.0/rathflow_cli/output.py +95 -0
  28. rathflow_cli-0.1.0/rathflow_cli/selftest.py +120 -0
  29. rathflow_cli-0.1.0/rathflow_cli/state.py +129 -0
  30. rathflow_cli-0.1.0/rathflow_cli.egg-info/PKG-INFO +164 -0
  31. rathflow_cli-0.1.0/rathflow_cli.egg-info/SOURCES.txt +34 -0
  32. rathflow_cli-0.1.0/rathflow_cli.egg-info/dependency_links.txt +1 -0
  33. rathflow_cli-0.1.0/rathflow_cli.egg-info/entry_points.txt +2 -0
  34. rathflow_cli-0.1.0/rathflow_cli.egg-info/requires.txt +5 -0
  35. rathflow_cli-0.1.0/rathflow_cli.egg-info/top_level.txt +1 -0
  36. rathflow_cli-0.1.0/setup.cfg +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rath Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,164 @@
1
+ Metadata-Version: 2.4
2
+ Name: rathflow-cli
3
+ Version: 0.1.0
4
+ Summary: RathFlow command line client: sessions, memory, sandboxes, assets and billing over the RathFlow REST API.
5
+ Author: Rath Team
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://rathflow.lynwe.com/
8
+ Project-URL: Repository, https://github.com/Rath-Team/rathflow-cli
9
+ Project-URL: Issues, https://github.com/Rath-Team/rathflow-cli/issues
10
+ Project-URL: Changelog, https://github.com/Rath-Team/rathflow-cli/blob/main/CHANGELOG.md
11
+ Keywords: rathflow,cli,agent,sessions,memory,sandbox
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development
22
+ Classifier: Topic :: Utilities
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: httpx>=0.27
27
+ Requires-Dist: typer>=0.12
28
+ Provides-Extra: socks
29
+ Requires-Dist: httpx[socks]; extra == "socks"
30
+ Dynamic: license-file
31
+
32
+ # rathflow-cli
33
+
34
+ [![PyPI](https://img.shields.io/pypi/v/rathflow-cli.svg)](https://pypi.org/project/rathflow-cli/)
35
+ [![Python](https://img.shields.io/pypi/pyversions/rathflow-cli.svg)](https://pypi.org/project/rathflow-cli/)
36
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
37
+
38
+ The RathFlow command line client. It talks to the RathFlow northbound REST API and
39
+ covers sessions, memory, sandboxes, agents, assets, projects, billing and
40
+ workflows.
41
+
42
+ ## Install
43
+
44
+ ```bash
45
+ uv tool install rathflow-cli
46
+ # or
47
+ pipx install rathflow-cli
48
+ # or
49
+ pip install rathflow-cli
50
+ ```
51
+
52
+ Requires Python 3.10+.
53
+
54
+ ## Quick start
55
+
56
+ ```bash
57
+ # Log in with your email and password (the password prompt does not echo).
58
+ rathflow auth login -e you@example.com
59
+
60
+ # Who am I, what can I reach.
61
+ rathflow whoami
62
+
63
+ # Pick a project scope; every later command carries it.
64
+ rathflow project list
65
+ rathflow project use <project_id>
66
+
67
+ # Read something.
68
+ rathflow session list
69
+ ```
70
+
71
+ The default gateway is `https://rathflow.lynwe.com`. Point it elsewhere with
72
+ `--base-url`, `RATHFLOW_BASE_URL`, or `rathflow config set base_url <url>`.
73
+
74
+ ## Commands
75
+
76
+ | Group | What it covers |
77
+ | --- | --- |
78
+ | `auth` | log in / out, register, API keys (`keys`, `key-create`, `key-revoke`) |
79
+ | `config` | local profiles (`list`, `show`, `set`, `use`) |
80
+ | `org` | organizations, invitations |
81
+ | `project` | projects, scope switching, project config, share links |
82
+ | `session` | sessions, blocks, events |
83
+ | `memory` | memory entries, search, commit tasks |
84
+ | `sandbox` | sandboxes, exec, code, files, logs |
85
+ | `agent` | agent definitions, versions, prompts, runs |
86
+ | `asset` | assets, versions, credentials, publish |
87
+ | `billing` | subscription, usage, invoices |
88
+ | `workflow` | workflows |
89
+ | `admin` | platform administration (platform admins only) |
90
+
91
+ Two more entry points sit at the root:
92
+
93
+ - `rathflow whoami` — identity, scope and reachable projects.
94
+ - `rathflow api <Key>` — call any endpoint directly; `rathflow api --list` prints
95
+ the whole table. Streaming endpoints stream.
96
+
97
+ Every group has `--help`, e.g. `rathflow session --help`.
98
+
99
+ ## Authentication
100
+
101
+ `rathflow auth login` exchanges your email and password for a JWT, stored in the
102
+ CLI config file (mode `0600`). The CLI sends it as a `Bearer` token and never
103
+ touches cookies, so it is unaffected by the browser CSRF rules.
104
+
105
+ For unattended use, issue a long-lived API key and pass it through the
106
+ environment:
107
+
108
+ ```bash
109
+ rathflow auth key-create # prints the key once, right here
110
+ export RATHFLOW_TOKEN=rf_... # or: rathflow config set token rf_...
111
+ ```
112
+
113
+ ## Configuration
114
+
115
+ Precedence, highest first: command-line flag > environment variable > active
116
+ profile > built-in default. Profiles live in
117
+ `~/.config/rathflow/config.json` (override the directory with
118
+ `RATHFLOW_CONFIG_DIR`).
119
+
120
+ | Variable | Meaning |
121
+ | --- | --- |
122
+ | `RATHFLOW_BASE_URL` | Gateway address |
123
+ | `RATHFLOW_PROJECT` | Project scope |
124
+ | `RATHFLOW_TOKEN` | Bearer token / API key |
125
+ | `RATHFLOW_CONFIG_DIR` | Config directory |
126
+
127
+ ## Output
128
+
129
+ - Default: human-readable tables.
130
+ - `--json`: one stable JSON document, for scripts.
131
+ - `--quiet` / `-q`: primary keys only.
132
+
133
+ Exit codes are stable: `0` success, and non-zero for usage errors, API errors and
134
+ remote command failures (a sandbox `exec` returns the remote exit code).
135
+
136
+ ## Proxies
137
+
138
+ The CLI respects the standard proxy variables. One caveat: `httpx` cannot use a
139
+ SOCKS proxy unless `socksio` is installed. If your environment sets
140
+ `ALL_PROXY=socks://...` (some Clash setups do), install the extra or the CLI
141
+ fails to start with `Unknown scheme for proxy URL`:
142
+
143
+ ```bash
144
+ uv tool install 'rathflow-cli[socks]'
145
+ # or clear it for this command only
146
+ ALL_PROXY= rathflow whoami
147
+ ```
148
+
149
+ ## Development
150
+
151
+ ```bash
152
+ uv venv --python 3.12 .venv
153
+ uv pip install -e .
154
+ .venv/bin/rathflow --help
155
+ .venv/bin/python -m rathflow_cli.selftest
156
+ ```
157
+
158
+ `selftest` checks that no command hard-codes a URL and, when the upstream
159
+ generated endpoint table is available (`RATHFLOW_ENDPOINTS_TS`), that
160
+ `rathflow_cli/endpoints.py` still matches it.
161
+
162
+ ## License
163
+
164
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,133 @@
1
+ # rathflow-cli
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/rathflow-cli.svg)](https://pypi.org/project/rathflow-cli/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/rathflow-cli.svg)](https://pypi.org/project/rathflow-cli/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
+
7
+ The RathFlow command line client. It talks to the RathFlow northbound REST API and
8
+ covers sessions, memory, sandboxes, agents, assets, projects, billing and
9
+ workflows.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ uv tool install rathflow-cli
15
+ # or
16
+ pipx install rathflow-cli
17
+ # or
18
+ pip install rathflow-cli
19
+ ```
20
+
21
+ Requires Python 3.10+.
22
+
23
+ ## Quick start
24
+
25
+ ```bash
26
+ # Log in with your email and password (the password prompt does not echo).
27
+ rathflow auth login -e you@example.com
28
+
29
+ # Who am I, what can I reach.
30
+ rathflow whoami
31
+
32
+ # Pick a project scope; every later command carries it.
33
+ rathflow project list
34
+ rathflow project use <project_id>
35
+
36
+ # Read something.
37
+ rathflow session list
38
+ ```
39
+
40
+ The default gateway is `https://rathflow.lynwe.com`. Point it elsewhere with
41
+ `--base-url`, `RATHFLOW_BASE_URL`, or `rathflow config set base_url <url>`.
42
+
43
+ ## Commands
44
+
45
+ | Group | What it covers |
46
+ | --- | --- |
47
+ | `auth` | log in / out, register, API keys (`keys`, `key-create`, `key-revoke`) |
48
+ | `config` | local profiles (`list`, `show`, `set`, `use`) |
49
+ | `org` | organizations, invitations |
50
+ | `project` | projects, scope switching, project config, share links |
51
+ | `session` | sessions, blocks, events |
52
+ | `memory` | memory entries, search, commit tasks |
53
+ | `sandbox` | sandboxes, exec, code, files, logs |
54
+ | `agent` | agent definitions, versions, prompts, runs |
55
+ | `asset` | assets, versions, credentials, publish |
56
+ | `billing` | subscription, usage, invoices |
57
+ | `workflow` | workflows |
58
+ | `admin` | platform administration (platform admins only) |
59
+
60
+ Two more entry points sit at the root:
61
+
62
+ - `rathflow whoami` — identity, scope and reachable projects.
63
+ - `rathflow api <Key>` — call any endpoint directly; `rathflow api --list` prints
64
+ the whole table. Streaming endpoints stream.
65
+
66
+ Every group has `--help`, e.g. `rathflow session --help`.
67
+
68
+ ## Authentication
69
+
70
+ `rathflow auth login` exchanges your email and password for a JWT, stored in the
71
+ CLI config file (mode `0600`). The CLI sends it as a `Bearer` token and never
72
+ touches cookies, so it is unaffected by the browser CSRF rules.
73
+
74
+ For unattended use, issue a long-lived API key and pass it through the
75
+ environment:
76
+
77
+ ```bash
78
+ rathflow auth key-create # prints the key once, right here
79
+ export RATHFLOW_TOKEN=rf_... # or: rathflow config set token rf_...
80
+ ```
81
+
82
+ ## Configuration
83
+
84
+ Precedence, highest first: command-line flag > environment variable > active
85
+ profile > built-in default. Profiles live in
86
+ `~/.config/rathflow/config.json` (override the directory with
87
+ `RATHFLOW_CONFIG_DIR`).
88
+
89
+ | Variable | Meaning |
90
+ | --- | --- |
91
+ | `RATHFLOW_BASE_URL` | Gateway address |
92
+ | `RATHFLOW_PROJECT` | Project scope |
93
+ | `RATHFLOW_TOKEN` | Bearer token / API key |
94
+ | `RATHFLOW_CONFIG_DIR` | Config directory |
95
+
96
+ ## Output
97
+
98
+ - Default: human-readable tables.
99
+ - `--json`: one stable JSON document, for scripts.
100
+ - `--quiet` / `-q`: primary keys only.
101
+
102
+ Exit codes are stable: `0` success, and non-zero for usage errors, API errors and
103
+ remote command failures (a sandbox `exec` returns the remote exit code).
104
+
105
+ ## Proxies
106
+
107
+ The CLI respects the standard proxy variables. One caveat: `httpx` cannot use a
108
+ SOCKS proxy unless `socksio` is installed. If your environment sets
109
+ `ALL_PROXY=socks://...` (some Clash setups do), install the extra or the CLI
110
+ fails to start with `Unknown scheme for proxy URL`:
111
+
112
+ ```bash
113
+ uv tool install 'rathflow-cli[socks]'
114
+ # or clear it for this command only
115
+ ALL_PROXY= rathflow whoami
116
+ ```
117
+
118
+ ## Development
119
+
120
+ ```bash
121
+ uv venv --python 3.12 .venv
122
+ uv pip install -e .
123
+ .venv/bin/rathflow --help
124
+ .venv/bin/python -m rathflow_cli.selftest
125
+ ```
126
+
127
+ `selftest` checks that no command hard-codes a URL and, when the upstream
128
+ generated endpoint table is available (`RATHFLOW_ENDPOINTS_TS`), that
129
+ `rathflow_cli/endpoints.py` still matches it.
130
+
131
+ ## License
132
+
133
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,47 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "rathflow-cli"
7
+ version = "0.1.0"
8
+ description = "RathFlow command line client: sessions, memory, sandboxes, assets and billing over the RathFlow REST API."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Rath Team" }]
14
+ keywords = ["rathflow", "cli", "agent", "sessions", "memory", "sandbox"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Environment :: Console",
18
+ "Intended Audience :: Developers",
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Topic :: Software Development",
26
+ "Topic :: Utilities",
27
+ ]
28
+ dependencies = [
29
+ "httpx>=0.27",
30
+ "typer>=0.12",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ # 走 SOCKS 代理(ALL_PROXY=socks5://…)时需要,见 README「代理」一节。
35
+ socks = ["httpx[socks]"]
36
+
37
+ [project.urls]
38
+ Homepage = "https://rathflow.lynwe.com/"
39
+ Repository = "https://github.com/Rath-Team/rathflow-cli"
40
+ Issues = "https://github.com/Rath-Team/rathflow-cli/issues"
41
+ Changelog = "https://github.com/Rath-Team/rathflow-cli/blob/main/CHANGELOG.md"
42
+
43
+ [project.scripts]
44
+ rathflow = "rathflow_cli.cli:main"
45
+
46
+ [tool.setuptools.packages.find]
47
+ include = ["rathflow_cli*"]
@@ -0,0 +1,6 @@
1
+ """RathFlow CLI —— RathFlow 北向 REST 的命令行客户端。
2
+
3
+ 安装:``uv tool install rathflow-cli``(或 ``pip install rathflow-cli``)。
4
+ """
5
+
6
+ __version__ = "0.1.0"
@@ -0,0 +1,6 @@
1
+ """支持 `python -m rathflow_cli`(与 `rathflow` 入口点等价)。"""
2
+
3
+ from .cli import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,161 @@
1
+ """typer 根应用与全局选项。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+
7
+ import typer
8
+
9
+ from . import __version__, errors, state as state_mod
10
+ from .state import State
11
+
12
+ app = typer.Typer(
13
+ add_completion=False,
14
+ no_args_is_help=True,
15
+ help="RathFlow 命令行客户端(JWT 登录;命令覆盖北向 REST 全部端点)。",
16
+ )
17
+
18
+
19
+ def _print_version(value: bool) -> None:
20
+ if value:
21
+ typer.echo(f"rathflow {__version__}")
22
+ raise typer.Exit()
23
+
24
+
25
+ @app.callback()
26
+ def root(
27
+ ctx: typer.Context,
28
+ profile: str = typer.Option(None, "--profile", "-p", help="配置档名(缺省 default)"),
29
+ base_url: str = typer.Option(None, "--base-url", help="gateway 地址(env RATHFLOW_BASE_URL)"),
30
+ project: str = typer.Option(None, "--project", help="项目作用域(env RATHFLOW_PROJECT)"),
31
+ json_out: bool = typer.Option(False, "--json", help="以 JSON 输出(脚本消费的稳定形态)"),
32
+ quiet: bool = typer.Option(False, "--quiet", "-q", help="只输出主键"),
33
+ version: bool = typer.Option(
34
+ False, "--version", callback=_print_version, is_eager=True, help="显示版本并退出"
35
+ ),
36
+ ) -> None:
37
+ ctx.obj = state_mod.install(
38
+ State(
39
+ profile_name=profile,
40
+ base_url=base_url,
41
+ project=project,
42
+ json_out=json_out,
43
+ quiet=quiet,
44
+ )
45
+ )
46
+
47
+
48
+ def _register() -> None:
49
+ from .commands import (
50
+ admin,
51
+ agent,
52
+ api_cmd,
53
+ asset,
54
+ auth_cmd,
55
+ billing,
56
+ config_cmd,
57
+ memory,
58
+ org,
59
+ project,
60
+ sandbox,
61
+ session,
62
+ workflow,
63
+ )
64
+
65
+ app.add_typer(auth_cmd.app, name="auth")
66
+ app.add_typer(config_cmd.app, name="config")
67
+ app.add_typer(org.app, name="org")
68
+ app.add_typer(project.app, name="project")
69
+ app.add_typer(session.app, name="session")
70
+ app.add_typer(memory.app, name="memory")
71
+ app.add_typer(sandbox.app, name="sandbox")
72
+ app.add_typer(agent.app, name="agent")
73
+ app.add_typer(asset.app, name="asset")
74
+ app.add_typer(billing.app, name="billing")
75
+ app.add_typer(workflow.app, name="workflow")
76
+ app.add_typer(admin.app, name="admin")
77
+
78
+ from .commands.auth_cmd import whoami
79
+
80
+ app.command("whoami", help="显示登录身份、作用域与可访问项目")(whoami)
81
+ # 逃生舱直接挂根:`rathflow api <Key>` 不必多打一层 `call`
82
+ app.command("api", help="任意端点调用(`rathflow api --list` 看全部)")(api_cmd.call)
83
+
84
+
85
+ _register()
86
+
87
+
88
+ # 可以挪到命令行最前的全局选项:前者是布尔旗标,后者各吃一个值。
89
+ _HOIST_FLAGS = frozenset({"--json", "--quiet"})
90
+ _HOIST_OPTS = frozenset({"--profile", "--base-url", "--project"})
91
+
92
+
93
+ def _value_opts() -> set[str]:
94
+ """全 app 里「吃下一个词」的选项名 —— 用来判断挪旗标安不安全。
95
+
96
+ 走 click 命令树而非 Typer 对象:Typer 只是注册表,click Group 是懒构建的。
97
+ """
98
+
99
+ def walk(cmd) -> set[str]:
100
+ found: set[str] = set()
101
+ for param in getattr(cmd, "params", []):
102
+ if getattr(param, "is_flag", False) or getattr(param, "count", False):
103
+ continue
104
+ found.update(getattr(param, "opts", []))
105
+ for sub in getattr(cmd, "commands", {}).values():
106
+ found |= walk(sub)
107
+ return found
108
+
109
+ return walk(typer.main.get_command(app))
110
+
111
+
112
+ def _hoist_globals(argv: list[str]) -> list[str]:
113
+ """把写在命令**后面**的全局选项挪到最前。
114
+
115
+ click 只认组级选项出现在子命令之前,故 `rathflow project list --json` 会被
116
+ 当成给 `list` 传参而报错。脚本里这个顺序太自然了,于是这里做一次改写:
117
+ 只挪白名单里的选项,且前一个词不是吃值的选项(否则那个词是被当值用的,
118
+ 例如 `--body --json` 里的 `--json` 是值,不能动)。
119
+ """
120
+ if len(argv) < 2:
121
+ return argv
122
+ value_opts = _value_opts()
123
+ head, tail = [argv[0]], []
124
+ hoisted: list[str] = []
125
+ idx = 1
126
+ while idx < len(argv):
127
+ tok = argv[idx]
128
+ prev = argv[idx - 1]
129
+ # 前一个词若是吃值的选项,本词就是它的值(click 也这么看),别动
130
+ safe = prev not in value_opts
131
+ if safe and tok in _HOIST_FLAGS:
132
+ hoisted.append(tok)
133
+ idx += 1
134
+ continue
135
+ if safe and tok in _HOIST_OPTS and idx + 1 < len(argv) and not argv[idx + 1].startswith("-"):
136
+ hoisted.extend([tok, argv[idx + 1]])
137
+ idx += 2
138
+ continue
139
+ tail.append(tok)
140
+ idx += 1
141
+ return head + hoisted + tail
142
+
143
+
144
+ def main() -> None:
145
+ sys.argv = _hoist_globals(sys.argv)
146
+ try:
147
+ app()
148
+ except errors.UsageError as exc:
149
+ print(f"错误:{exc}", file=sys.stderr)
150
+ sys.exit(errors.EXIT_USAGE)
151
+ except errors.ApiError as exc:
152
+ print(f"错误:{exc}", file=sys.stderr)
153
+ for detail in exc.details:
154
+ print(f" - {detail}", file=sys.stderr)
155
+ sys.exit(exc.exit_code)
156
+ except KeyboardInterrupt: # pragma: no cover
157
+ sys.exit(130)
158
+
159
+
160
+ if __name__ == "__main__": # pragma: no cover
161
+ main()
File without changes
@@ -0,0 +1,78 @@
1
+ """命令间共用的小工具(参数解析与请求体装配)。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sys
7
+
8
+ import typer
9
+
10
+ from ..errors import UsageError
11
+
12
+ PATH_OPT = typer.Option(None, "--path", help="路径参数 k=v,可重复(如 --path session_id=abc)")
13
+ QUERY_OPT = typer.Option(None, "--query", help="查询参数 k=v,可重复")
14
+ BODY_OPT = typer.Option(None, "--body", help="请求体 JSON 字面量")
15
+ BODY_FILE_OPT = typer.Option(None, "--body-file", help="从文件读请求体 JSON(- 读 stdin)")
16
+ PAGE_SIZE_OPT = typer.Option(None, "--page-size", help="分页大小")
17
+ PAGE_TOKEN_OPT = typer.Option(None, "--page-token", help="分页游标")
18
+
19
+
20
+ def pairs(items: list[str] | None) -> dict:
21
+ """['k=v', ...] → {k: v};值里的 = 保留(只切第一个)。"""
22
+ out: dict[str, str] = {}
23
+ for item in items or []:
24
+ if "=" not in item:
25
+ raise UsageError(f"参数需形如 k=v,收到 {item!r}")
26
+ key, _, value = item.partition("=")
27
+ out[key.strip()] = value
28
+ return out
29
+
30
+
31
+ def body_of(body: str | None, body_file: str | None) -> dict | None:
32
+ """装配请求体:字面量或文件(`-` = stdin)。两者互斥。"""
33
+ if body and body_file:
34
+ raise UsageError("--body 与 --body-file 不能同时用")
35
+ raw: str | None = None
36
+ if body_file:
37
+ raw = sys.stdin.read() if body_file == "-" else _read(body_file)
38
+ elif body:
39
+ raw = body
40
+ else:
41
+ return None
42
+ raw = raw.strip()
43
+ if not raw:
44
+ return {}
45
+ try:
46
+ parsed = json.loads(raw)
47
+ except json.JSONDecodeError as exc:
48
+ raise UsageError(f"请求体不是合法 JSON:{exc}") from None
49
+ if not isinstance(parsed, dict):
50
+ raise UsageError("请求体必须是 JSON 对象")
51
+ return parsed
52
+
53
+
54
+ def _read(path: str) -> str:
55
+ try:
56
+ with open(path, encoding="utf-8") as fh:
57
+ return fh.read()
58
+ except OSError as exc:
59
+ raise UsageError(f"读取请求体文件失败:{exc}") from None
60
+
61
+
62
+ def pagination(page_size: int | None, page_token: str | None, extra: dict | None = None) -> dict:
63
+ """装配分页查询串(protojson 查询名用 proto 字段名 snake_case)。"""
64
+ query = dict(extra or {})
65
+ if page_size is not None:
66
+ query["page_size"] = page_size
67
+ if page_token:
68
+ query["page_token"] = page_token
69
+ return query
70
+
71
+
72
+ def id_of(payload: dict, *keys: str) -> str:
73
+ """从响应里挑主键用于 -q 输出。"""
74
+ for key in keys:
75
+ value = (payload or {}).get(key)
76
+ if value:
77
+ return str(value)
78
+ return ""