@josephyan/qingflow-cli 0.2.0-beta.55 → 0.2.0-beta.56

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.
package/README.md CHANGED
@@ -3,13 +3,13 @@
3
3
  Install:
4
4
 
5
5
  ```bash
6
- npm install @josephyan/qingflow-cli@0.2.0-beta.55
6
+ npm install @josephyan/qingflow-cli@0.2.0-beta.56
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @josephyan/qingflow-cli@0.2.0-beta.55 qingflow
12
+ npx -y -p @josephyan/qingflow-cli@0.2.0-beta.56 qingflow
13
13
  ```
14
14
 
15
15
  Environment:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@josephyan/qingflow-cli",
3
- "version": "0.2.0-beta.55",
3
+ "version": "0.2.0-beta.56",
4
4
  "description": "Human-friendly Qingflow command line interface for auth, record operations, import, tasks, and stable builder flows.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qingflow-mcp"
7
- version = "0.2.0b55"
7
+ version = "0.2.0b56"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -6,29 +6,29 @@ from ..context import CliContext
6
6
 
7
7
 
8
8
  def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
9
- parser = subparsers.add_parser("app", help="应用发现")
10
- app_subparsers = parser.add_subparsers(dest="app_command", required=True)
9
+ parser = subparsers.add_parser("apps", help="应用发现")
10
+ app_subparsers = parser.add_subparsers(dest="apps_command", required=True)
11
11
 
12
12
  list_parser = app_subparsers.add_parser("list", help="列出可见应用")
13
- list_parser.set_defaults(handler=_handle_list, format_hint="app_list")
13
+ list_parser.set_defaults(handler=_handle_list, format_hint="apps_list")
14
14
 
15
- search = app_subparsers.add_parser("search", help="搜索应用")
16
- search.add_argument("--keyword", default="")
17
- search.add_argument("--page", type=int, default=1)
18
- search.add_argument("--page-size", type=int, default=50)
19
- search.set_defaults(handler=_handle_search, format_hint="app_search")
15
+ find = app_subparsers.add_parser("find", help="搜索应用")
16
+ find.add_argument("--keyword", default="")
17
+ find.add_argument("--page", type=int, default=1)
18
+ find.add_argument("--page-size", type=int, default=50)
19
+ find.set_defaults(handler=_handle_find, format_hint="apps_list")
20
20
 
21
- get = app_subparsers.add_parser("get", help="读取应用可访问视图与导入能力")
22
- get.add_argument("--app-key", required=True)
23
- get.set_defaults(handler=_handle_get, format_hint="app_get")
21
+ show = app_subparsers.add_parser("show", help="读取应用信息")
22
+ show.add_argument("--app", required=True)
23
+ show.set_defaults(handler=_handle_show, format_hint="app_show")
24
24
 
25
25
 
26
26
  def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
27
- return context.app.app_list(profile=args.profile)
27
+ return context.apps.list(profile=args.profile)
28
28
 
29
29
 
30
- def _handle_search(args: argparse.Namespace, context: CliContext) -> dict:
31
- return context.app.app_search(
30
+ def _handle_find(args: argparse.Namespace, context: CliContext) -> dict:
31
+ return context.apps.find(
32
32
  profile=args.profile,
33
33
  keyword=args.keyword,
34
34
  page_num=args.page,
@@ -36,5 +36,5 @@ def _handle_search(args: argparse.Namespace, context: CliContext) -> dict:
36
36
  )
37
37
 
38
38
 
39
- def _handle_get(args: argparse.Namespace, context: CliContext) -> dict:
40
- return context.app.app_get(profile=args.profile, app_key=args.app_key)
39
+ def _handle_show(args: argparse.Namespace, context: CliContext) -> dict:
40
+ return context.apps.show(profile=args.profile, app_key=args.app)
@@ -10,33 +10,34 @@ from .common import read_secret_arg
10
10
 
11
11
 
12
12
  def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
13
- parser = subparsers.add_parser("auth", help="认证与会话")
14
- auth_subparsers = parser.add_subparsers(dest="auth_command", required=True)
13
+ me = subparsers.add_parser("me", help="查看当前会话")
14
+ me.set_defaults(handler=_handle_me, format_hint="me")
15
15
 
16
- login = auth_subparsers.add_parser("login", help="邮箱密码登录")
16
+ login = subparsers.add_parser("login", help="邮箱密码登录")
17
17
  login.add_argument("--base-url")
18
18
  login.add_argument("--qf-version")
19
19
  login.add_argument("--email", required=True)
20
20
  login.add_argument("--password")
21
21
  login.add_argument("--password-stdin", action="store_true")
22
22
  login.add_argument("--persist", action=argparse.BooleanOptionalAction, default=True)
23
- login.set_defaults(handler=_handle_login, format_hint="auth_whoami")
23
+ login.set_defaults(handler=_handle_login, format_hint="session")
24
24
 
25
- use_token = auth_subparsers.add_parser("use-token", help="直接注入 token")
25
+ use_token = subparsers.add_parser("use-token", help="直接接入 token")
26
26
  use_token.add_argument("--base-url")
27
27
  use_token.add_argument("--qf-version")
28
28
  use_token.add_argument("--token")
29
29
  use_token.add_argument("--token-stdin", action="store_true")
30
- use_token.add_argument("--ws-id", type=int)
30
+ use_token.add_argument("--ws", dest="ws_id", type=int)
31
31
  use_token.add_argument("--persist", action=argparse.BooleanOptionalAction, default=False)
32
- use_token.set_defaults(handler=_handle_use_token, format_hint="auth_whoami")
32
+ use_token.set_defaults(handler=_handle_use_token, format_hint="session")
33
33
 
34
- whoami = auth_subparsers.add_parser("whoami", help="查看当前登录态")
35
- whoami.set_defaults(handler=_handle_whoami, format_hint="auth_whoami")
36
-
37
- logout = auth_subparsers.add_parser("logout", help="退出登录")
34
+ logout = subparsers.add_parser("logout", help="退出当前会话")
38
35
  logout.add_argument("--forget-persisted", action="store_true")
39
- logout.set_defaults(handler=_handle_logout, format_hint="")
36
+ logout.set_defaults(handler=_handle_logout, format_hint="session")
37
+
38
+
39
+ def _handle_me(args: argparse.Namespace, context: CliContext) -> dict:
40
+ return context.auth.me(profile=args.profile)
40
41
 
41
42
 
42
43
  def _handle_login(args: argparse.Namespace, context: CliContext) -> dict:
@@ -48,7 +49,7 @@ def _handle_login(args: argparse.Namespace, context: CliContext) -> dict:
48
49
  password = getpass.getpass("Password: ")
49
50
  else:
50
51
  raise QingflowApiError.config_error("password is required; use --password or --password-stdin")
51
- return context.auth.auth_login(
52
+ return context.auth.login(
52
53
  profile=args.profile,
53
54
  base_url=args.base_url,
54
55
  qf_version=args.qf_version,
@@ -60,7 +61,7 @@ def _handle_login(args: argparse.Namespace, context: CliContext) -> dict:
60
61
 
61
62
  def _handle_use_token(args: argparse.Namespace, context: CliContext) -> dict:
62
63
  token = read_secret_arg(args.token, stdin_enabled=bool(args.token_stdin), label="token")
63
- return context.auth.auth_use_token(
64
+ return context.auth.use_token(
64
65
  profile=args.profile,
65
66
  base_url=args.base_url,
66
67
  qf_version=args.qf_version,
@@ -70,9 +71,5 @@ def _handle_use_token(args: argparse.Namespace, context: CliContext) -> dict:
70
71
  )
71
72
 
72
73
 
73
- def _handle_whoami(args: argparse.Namespace, context: CliContext) -> dict:
74
- return context.auth.auth_whoami(profile=args.profile)
75
-
76
-
77
74
  def _handle_logout(args: argparse.Namespace, context: CliContext) -> dict:
78
- return context.auth.auth_logout(profile=args.profile, forget_persisted=bool(args.forget_persisted))
75
+ return context.auth.logout(profile=args.profile, forget_persisted=bool(args.forget_persisted))
@@ -1,184 +1,199 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import argparse
4
+ from typing import Any
4
5
 
6
+ from ...errors import QingflowApiError
5
7
  from ..context import CliContext
6
- from .common import load_list_arg, load_object_arg, require_list_arg
8
+ from .common import add_file_arg, add_stdin_json_flag, load_object_input
7
9
 
8
10
 
9
11
  def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
10
- parser = subparsers.add_parser("builder", help="稳定 builder 命令")
11
- builder_subparsers = parser.add_subparsers(dest="builder_command", required=True)
12
+ parser = subparsers.add_parser("build", help="稳定搭建能力")
13
+ build_subparsers = parser.add_subparsers(dest="build_command", required=True)
12
14
 
13
- package = builder_subparsers.add_parser("package", help="应用包")
14
- package_subparsers = package.add_subparsers(dest="builder_package_command", required=True)
15
+ package = build_subparsers.add_parser("package", help="应用包")
16
+ package_subparsers = package.add_subparsers(dest="build_package_command", required=True)
15
17
  package_resolve = package_subparsers.add_parser("resolve", help="解析应用包")
16
- package_resolve.add_argument("--package-name", required=True)
17
- package_resolve.set_defaults(handler=_handle_package_resolve, format_hint="builder_summary")
18
-
19
- app = builder_subparsers.add_parser("app", help="应用")
20
- app_subparsers = app.add_subparsers(dest="builder_app_command", required=True)
18
+ package_resolve.add_argument("--name", required=True)
19
+ package_resolve.set_defaults(handler=_handle_package_resolve, format_hint="builder_result")
21
20
 
21
+ app = build_subparsers.add_parser("app", help="应用")
22
+ app_subparsers = app.add_subparsers(dest="build_app_command", required=True)
22
23
  app_resolve = app_subparsers.add_parser("resolve", help="解析应用")
23
24
  app_resolve.add_argument("--app-key", default="")
24
- app_resolve.add_argument("--app-name", default="")
25
+ app_resolve.add_argument("--name", default="")
25
26
  app_resolve.add_argument("--package-tag-id", type=int)
26
- app_resolve.set_defaults(handler=_handle_app_resolve, format_hint="builder_summary")
27
-
28
- for name, help_text, handler in [
29
- ("read-summary", "读取应用摘要", _handle_app_read_summary),
30
- ("read-fields", "读取字段摘要", _handle_app_read_fields),
31
- ("read-layout", "读取布局摘要", _handle_app_read_layout),
32
- ("read-views", "读取视图摘要", _handle_app_read_views),
33
- ("read-flow", "读取流程摘要", _handle_app_read_flow),
34
- ("read-charts", "读取报表摘要", _handle_app_read_charts),
35
- ]:
36
- sub = app_subparsers.add_parser(name, help=help_text)
37
- sub.add_argument("--app-key", required=True)
38
- sub.set_defaults(handler=handler, format_hint="builder_summary")
39
-
40
- portal = builder_subparsers.add_parser("portal", help="门户")
41
- portal_subparsers = portal.add_subparsers(dest="builder_portal_command", required=True)
42
- portal_read = portal_subparsers.add_parser("read-summary", help="读取门户摘要")
43
- portal_read.add_argument("--dash-key", required=True)
44
- portal_read.add_argument("--being-draft", action=argparse.BooleanOptionalAction, default=True)
45
- portal_read.set_defaults(handler=_handle_portal_read_summary, format_hint="builder_summary")
46
-
47
- portal_apply = portal_subparsers.add_parser("apply", help="更新门户")
48
- portal_apply.add_argument("--dash-key", default="")
49
- portal_apply.add_argument("--dash-name", default="")
27
+ app_resolve.set_defaults(handler=_handle_app_resolve, format_hint="builder_result")
28
+
29
+ app_show = app_subparsers.add_parser("show", help="读取应用摘要")
30
+ app_show.add_argument("--app", required=True)
31
+ app_show.set_defaults(handler=_handle_app_show, format_hint="builder_result")
32
+
33
+ fields = build_subparsers.add_parser("fields", help="字段")
34
+ fields_subparsers = fields.add_subparsers(dest="build_fields_command", required=True)
35
+ fields_show = fields_subparsers.add_parser("show", help="读取字段摘要")
36
+ fields_show.add_argument("--app", required=True)
37
+ fields_show.set_defaults(handler=_handle_fields_show, format_hint="builder_result")
38
+ fields_apply = fields_subparsers.add_parser("apply", help="执行字段变更")
39
+ fields_apply.add_argument("--app", default="")
40
+ fields_apply.add_argument("--package-tag-id", type=int)
41
+ fields_apply.add_argument("--name", dest="app_name", default="")
42
+ fields_apply.add_argument("--title", dest="app_title", default="")
43
+ fields_apply.add_argument("--create-if-missing", action="store_true")
44
+ fields_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
45
+ add_file_arg(fields_apply, required=False, help_text="读取字段变更 JSON,支持 add_fields/update_fields/remove_fields")
46
+ add_stdin_json_flag(fields_apply)
47
+ fields_apply.set_defaults(handler=_handle_fields_apply, format_hint="builder_result")
48
+
49
+ layout = build_subparsers.add_parser("layout", help="布局")
50
+ layout_subparsers = layout.add_subparsers(dest="build_layout_command", required=True)
51
+ layout_show = layout_subparsers.add_parser("show", help="读取布局摘要")
52
+ layout_show.add_argument("--app", required=True)
53
+ layout_show.set_defaults(handler=_handle_layout_show, format_hint="builder_result")
54
+
55
+ views = build_subparsers.add_parser("views", help="视图")
56
+ views_subparsers = views.add_subparsers(dest="build_views_command", required=True)
57
+ views_show = views_subparsers.add_parser("show", help="读取视图摘要")
58
+ views_show.add_argument("--app", required=True)
59
+ views_show.set_defaults(handler=_handle_views_show, format_hint="builder_result")
60
+
61
+ flow = build_subparsers.add_parser("flow", help="流程")
62
+ flow_subparsers = flow.add_subparsers(dest="build_flow_command", required=True)
63
+ flow_show = flow_subparsers.add_parser("show", help="读取流程摘要")
64
+ flow_show.add_argument("--app", required=True)
65
+ flow_show.set_defaults(handler=_handle_flow_show, format_hint="builder_result")
66
+
67
+ charts = build_subparsers.add_parser("charts", help="报表")
68
+ charts_subparsers = charts.add_subparsers(dest="build_charts_command", required=True)
69
+ charts_show = charts_subparsers.add_parser("show", help="读取报表摘要")
70
+ charts_show.add_argument("--app", required=True)
71
+ charts_show.set_defaults(handler=_handle_charts_show, format_hint="builder_result")
72
+ charts_apply = charts_subparsers.add_parser("apply", help="执行报表变更")
73
+ charts_apply.add_argument("--app", required=True)
74
+ add_file_arg(charts_apply, required=False, help_text="读取报表变更 JSON,支持 upsert_charts/remove_chart_ids/reorder_chart_ids")
75
+ add_stdin_json_flag(charts_apply)
76
+ charts_apply.set_defaults(handler=_handle_charts_apply, format_hint="builder_result")
77
+
78
+ portal = build_subparsers.add_parser("portal", help="门户")
79
+ portal_subparsers = portal.add_subparsers(dest="build_portal_command", required=True)
80
+ portal_show = portal_subparsers.add_parser("show", help="读取门户摘要")
81
+ portal_show.add_argument("--dash", required=True)
82
+ portal_show.add_argument("--draft", action=argparse.BooleanOptionalAction, default=True)
83
+ portal_show.set_defaults(handler=_handle_portal_show, format_hint="builder_result")
84
+ portal_apply = portal_subparsers.add_parser("apply", help="执行门户变更")
85
+ portal_apply.add_argument("--dash", default="")
86
+ portal_apply.add_argument("--name", dest="dash_name", default="")
50
87
  portal_apply.add_argument("--package-tag-id", type=int)
51
88
  portal_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
52
- portal_apply.add_argument("--sections-file", required=True)
53
- portal_apply.add_argument("--auth-file")
54
- portal_apply.add_argument("--icon")
55
- portal_apply.add_argument("--color")
56
- portal_apply.add_argument("--hide-copyright", action=argparse.BooleanOptionalAction, default=None)
57
- portal_apply.add_argument("--dash-global-config-file")
58
- portal_apply.add_argument("--config-file")
59
- portal_apply.set_defaults(handler=_handle_portal_apply, format_hint="builder_summary")
60
-
61
- schema_apply = builder_subparsers.add_parser("schema", help="字段搭建")
62
- schema_apply_subparsers = schema_apply.add_subparsers(dest="builder_schema_command", required=True)
63
- schema_apply_apply = schema_apply_subparsers.add_parser("apply", help="执行字段变更")
64
- schema_apply_apply.add_argument("--app-key", default="")
65
- schema_apply_apply.add_argument("--package-tag-id", type=int)
66
- schema_apply_apply.add_argument("--app-name", default="")
67
- schema_apply_apply.add_argument("--app-title", default="")
68
- schema_apply_apply.add_argument("--create-if-missing", action="store_true")
69
- schema_apply_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
70
- schema_apply_apply.add_argument("--add-fields-file")
71
- schema_apply_apply.add_argument("--update-fields-file")
72
- schema_apply_apply.add_argument("--remove-fields-file")
73
- schema_apply_apply.set_defaults(handler=_handle_schema_apply, format_hint="builder_summary")
74
-
75
- charts_apply = builder_subparsers.add_parser("charts", help="报表")
76
- charts_apply_subparsers = charts_apply.add_subparsers(dest="builder_charts_command", required=True)
77
- charts_apply_apply = charts_apply_subparsers.add_parser("apply", help="执行报表变更")
78
- charts_apply_apply.add_argument("--app-key", required=True)
79
- charts_apply_apply.add_argument("--upsert-file")
80
- charts_apply_apply.add_argument("--remove-chart-ids-file")
81
- charts_apply_apply.add_argument("--reorder-chart-ids-file")
82
- charts_apply_apply.set_defaults(handler=_handle_charts_apply, format_hint="builder_summary")
83
-
84
- publish_verify = builder_subparsers.add_parser("publish", help="发布校验")
85
- publish_verify_subparsers = publish_verify.add_subparsers(dest="builder_publish_command", required=True)
86
- publish_verify_verify = publish_verify_subparsers.add_parser("verify", help="校验应用发布")
87
- publish_verify_verify.add_argument("--app-key", required=True)
88
- publish_verify_verify.add_argument("--expected-package-tag-id", type=int)
89
- publish_verify_verify.set_defaults(handler=_handle_publish_verify, format_hint="builder_summary")
89
+ add_file_arg(portal_apply, required=True, help_text="读取门户变更 JSON,至少包含 sections")
90
+ add_stdin_json_flag(portal_apply)
91
+ portal_apply.set_defaults(handler=_handle_portal_apply, format_hint="builder_result")
92
+
93
+ publish = build_subparsers.add_parser("publish", help="发布校验")
94
+ publish_subparsers = publish.add_subparsers(dest="build_publish_command", required=True)
95
+ publish_verify = publish_subparsers.add_parser("verify", help="校验应用发布")
96
+ publish_verify.add_argument("--app", required=True)
97
+ publish_verify.add_argument("--expected-package-tag-id", type=int)
98
+ publish_verify.set_defaults(handler=_handle_publish_verify, format_hint="builder_result")
90
99
 
91
100
 
92
101
  def _handle_package_resolve(args: argparse.Namespace, context: CliContext) -> dict:
93
- return context.builder.package_resolve(profile=args.profile, package_name=args.package_name)
102
+ return context.build.package_resolve(profile=args.profile, package_name=args.name)
94
103
 
95
104
 
96
105
  def _handle_app_resolve(args: argparse.Namespace, context: CliContext) -> dict:
97
- return context.builder.app_resolve(
106
+ return context.build.app_resolve(
98
107
  profile=args.profile,
99
108
  app_key=args.app_key,
100
- app_name=args.app_name,
109
+ app_name=args.name,
101
110
  package_tag_id=args.package_tag_id,
102
111
  )
103
112
 
104
113
 
105
- def _handle_app_read_summary(args: argparse.Namespace, context: CliContext) -> dict:
106
- return context.builder.app_read_summary(profile=args.profile, app_key=args.app_key)
114
+ def _handle_app_show(args: argparse.Namespace, context: CliContext) -> dict:
115
+ return context.build.app_show(profile=args.profile, app_key=args.app)
107
116
 
108
117
 
109
- def _handle_app_read_fields(args: argparse.Namespace, context: CliContext) -> dict:
110
- return context.builder.app_read_fields(profile=args.profile, app_key=args.app_key)
118
+ def _handle_fields_show(args: argparse.Namespace, context: CliContext) -> dict:
119
+ return context.build.fields_show(profile=args.profile, app_key=args.app)
111
120
 
112
121
 
113
- def _handle_app_read_layout(args: argparse.Namespace, context: CliContext) -> dict:
114
- return context.builder.app_read_layout_summary(profile=args.profile, app_key=args.app_key)
122
+ def _handle_fields_apply(args: argparse.Namespace, context: CliContext) -> dict:
123
+ payload = load_object_input(args, required=False)
124
+ return context.build.fields_apply(
125
+ profile=args.profile,
126
+ app_key=args.app,
127
+ package_tag_id=args.package_tag_id,
128
+ app_name=args.app_name,
129
+ app_title=args.app_title,
130
+ create_if_missing=bool(args.create_if_missing),
131
+ publish=bool(args.publish),
132
+ add_fields=_coerce_list(payload.get("add_fields")),
133
+ update_fields=_coerce_list(payload.get("update_fields")),
134
+ remove_fields=_coerce_list(payload.get("remove_fields")),
135
+ )
115
136
 
116
137
 
117
- def _handle_app_read_views(args: argparse.Namespace, context: CliContext) -> dict:
118
- return context.builder.app_read_views_summary(profile=args.profile, app_key=args.app_key)
138
+ def _handle_layout_show(args: argparse.Namespace, context: CliContext) -> dict:
139
+ return context.build.layout_show(profile=args.profile, app_key=args.app)
119
140
 
120
141
 
121
- def _handle_app_read_flow(args: argparse.Namespace, context: CliContext) -> dict:
122
- return context.builder.app_read_flow_summary(profile=args.profile, app_key=args.app_key)
142
+ def _handle_views_show(args: argparse.Namespace, context: CliContext) -> dict:
143
+ return context.build.views_show(profile=args.profile, app_key=args.app)
123
144
 
124
145
 
125
- def _handle_app_read_charts(args: argparse.Namespace, context: CliContext) -> dict:
126
- return context.builder.app_read_charts_summary(profile=args.profile, app_key=args.app_key)
146
+ def _handle_flow_show(args: argparse.Namespace, context: CliContext) -> dict:
147
+ return context.build.flow_show(profile=args.profile, app_key=args.app)
127
148
 
128
149
 
129
- def _handle_portal_read_summary(args: argparse.Namespace, context: CliContext) -> dict:
130
- return context.builder.portal_read_summary(
131
- profile=args.profile,
132
- dash_key=args.dash_key,
133
- being_draft=bool(args.being_draft),
134
- )
150
+ def _handle_charts_show(args: argparse.Namespace, context: CliContext) -> dict:
151
+ return context.build.charts_show(profile=args.profile, app_key=args.app)
135
152
 
136
153
 
137
- def _handle_schema_apply(args: argparse.Namespace, context: CliContext) -> dict:
138
- return context.builder.app_schema_apply(
154
+ def _handle_charts_apply(args: argparse.Namespace, context: CliContext) -> dict:
155
+ payload = load_object_input(args, required=False)
156
+ return context.build.charts_apply(
139
157
  profile=args.profile,
140
- app_key=args.app_key,
141
- package_tag_id=args.package_tag_id,
142
- app_name=args.app_name,
143
- app_title=args.app_title,
144
- create_if_missing=bool(args.create_if_missing),
145
- publish=bool(args.publish),
146
- add_fields=load_list_arg(args.add_fields_file, option_name="--add-fields-file"),
147
- update_fields=load_list_arg(args.update_fields_file, option_name="--update-fields-file"),
148
- remove_fields=load_list_arg(args.remove_fields_file, option_name="--remove-fields-file"),
158
+ app_key=args.app,
159
+ upsert_charts=_coerce_list(payload.get("upsert_charts")),
160
+ remove_chart_ids=_coerce_list(payload.get("remove_chart_ids")),
161
+ reorder_chart_ids=_coerce_list(payload.get("reorder_chart_ids")),
149
162
  )
150
163
 
151
164
 
152
- def _handle_charts_apply(args: argparse.Namespace, context: CliContext) -> dict:
153
- return context.builder.app_charts_apply(
154
- profile=args.profile,
155
- app_key=args.app_key,
156
- upsert_charts=load_list_arg(args.upsert_file, option_name="--upsert-file"),
157
- remove_chart_ids=load_list_arg(args.remove_chart_ids_file, option_name="--remove-chart-ids-file"),
158
- reorder_chart_ids=load_list_arg(args.reorder_chart_ids_file, option_name="--reorder-chart-ids-file"),
159
- )
165
+ def _handle_portal_show(args: argparse.Namespace, context: CliContext) -> dict:
166
+ return context.build.portal_show(profile=args.profile, dash_key=args.dash, being_draft=bool(args.draft))
160
167
 
161
168
 
162
169
  def _handle_portal_apply(args: argparse.Namespace, context: CliContext) -> dict:
163
- return context.builder.portal_apply(
170
+ payload = load_object_input(args, required=True)
171
+ sections = payload.get("sections")
172
+ if not isinstance(sections, list) or not sections:
173
+ raise QingflowApiError.config_error("sections must be a non-empty list")
174
+ return context.build.portal_apply(
164
175
  profile=args.profile,
165
- dash_key=args.dash_key,
176
+ dash_key=args.dash,
166
177
  dash_name=args.dash_name,
167
178
  package_tag_id=args.package_tag_id,
168
179
  publish=bool(args.publish),
169
- sections=require_list_arg(args.sections_file, option_name="--sections-file"),
170
- auth=load_object_arg(args.auth_file, option_name="--auth-file"),
171
- icon=args.icon,
172
- color=args.color,
173
- hide_copyright=args.hide_copyright,
174
- dash_global_config=load_object_arg(args.dash_global_config_file, option_name="--dash-global-config-file"),
175
- config=load_object_arg(args.config_file, option_name="--config-file"),
180
+ sections=sections,
181
+ auth=payload.get("auth") if isinstance(payload.get("auth"), dict) else None,
182
+ icon=payload.get("icon") if isinstance(payload.get("icon"), str) else None,
183
+ color=payload.get("color") if isinstance(payload.get("color"), str) else None,
184
+ hide_copyright=payload.get("hide_copyright") if isinstance(payload.get("hide_copyright"), bool) else None,
185
+ dash_global_config=payload.get("dash_global_config") if isinstance(payload.get("dash_global_config"), dict) else None,
186
+ config=payload.get("config") if isinstance(payload.get("config"), dict) else None,
176
187
  )
177
188
 
178
189
 
179
190
  def _handle_publish_verify(args: argparse.Namespace, context: CliContext) -> dict:
180
- return context.builder.app_publish_verify(
191
+ return context.build.publish_verify(
181
192
  profile=args.profile,
182
- app_key=args.app_key,
193
+ app_key=args.app,
183
194
  expected_package_tag_id=args.expected_package_tag_id,
184
195
  )
196
+
197
+
198
+ def _coerce_list(value: Any) -> list[Any]:
199
+ return value if isinstance(value, list) else []
@@ -1,11 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import argparse
4
+ import json
4
5
  import sys
5
6
  from typing import Any
6
7
 
7
8
  from ...errors import QingflowApiError
8
- from ..json_io import load_json_list, load_json_object, load_optional_json_list, load_optional_json_object
9
+ from ..json_io import load_json_list, load_json_object
9
10
 
10
11
 
11
12
  def parse_bool_text(value: str) -> bool:
@@ -17,26 +18,6 @@ def parse_bool_text(value: str) -> bool:
17
18
  raise argparse.ArgumentTypeError("expected one of: true, false, 1, 0, yes, no")
18
19
 
19
20
 
20
- def load_list_arg(path: str | None, *, option_name: str) -> list[Any]:
21
- return load_optional_json_list(path, option_name=option_name)
22
-
23
-
24
- def load_object_arg(path: str | None, *, option_name: str) -> dict[str, Any] | None:
25
- return load_optional_json_object(path, option_name=option_name)
26
-
27
-
28
- def require_list_arg(path: str | None, *, option_name: str) -> list[Any]:
29
- if not path:
30
- raise QingflowApiError.config_error(f"{option_name} is required")
31
- return load_json_list(path, option_name=option_name)
32
-
33
-
34
- def require_object_arg(path: str | None, *, option_name: str) -> dict[str, Any]:
35
- if not path:
36
- raise QingflowApiError.config_error(f"{option_name} is required")
37
- return load_json_object(path, option_name=option_name)
38
-
39
-
40
21
  def read_secret_arg(value: str | None, *, stdin_enabled: bool, label: str) -> str:
41
22
  if stdin_enabled:
42
23
  secret = sys.stdin.read().strip()
@@ -45,3 +26,96 @@ def read_secret_arg(value: str | None, *, stdin_enabled: bool, label: str) -> st
45
26
  if value:
46
27
  return value
47
28
  raise QingflowApiError.config_error(f"{label} is required")
29
+
30
+
31
+ def add_stdin_json_flag(parser: argparse.ArgumentParser) -> None:
32
+ parser.add_argument("--stdin-json", action="store_true", help="从标准输入读取 JSON")
33
+
34
+
35
+ def add_file_arg(parser: argparse.ArgumentParser, *, required: bool = False, help_text: str = "从文件读取 JSON") -> None:
36
+ parser.add_argument("--file", required=required, help=help_text)
37
+
38
+
39
+ def add_field_args(parser: argparse.ArgumentParser) -> None:
40
+ parser.add_argument("--field", dest="fields", action="append", default=[], help="字段赋值,格式:字段名=值")
41
+
42
+
43
+ def load_object_input(args: argparse.Namespace, *, required: bool = False) -> dict[str, Any]:
44
+ if getattr(args, "stdin_json", False):
45
+ payload = _read_stdin_json()
46
+ if not isinstance(payload, dict):
47
+ raise QingflowApiError.config_error("stdin JSON must be an object")
48
+ return payload
49
+ file_path = getattr(args, "file", None)
50
+ if file_path:
51
+ return load_json_object(file_path, option_name="--file")
52
+ if required:
53
+ raise QingflowApiError.config_error("either --stdin-json or --file is required")
54
+ return {}
55
+
56
+
57
+ def load_list_input(args: argparse.Namespace, *, required: bool = False) -> list[Any]:
58
+ if getattr(args, "stdin_json", False):
59
+ payload = _read_stdin_json()
60
+ if not isinstance(payload, list):
61
+ raise QingflowApiError.config_error("stdin JSON must be a list")
62
+ return payload
63
+ file_path = getattr(args, "file", None)
64
+ if file_path:
65
+ return load_json_list(file_path, option_name="--file")
66
+ if required:
67
+ raise QingflowApiError.config_error("either --stdin-json or --file is required")
68
+ return []
69
+
70
+
71
+ def parse_field_assignments(items: list[str]) -> dict[str, Any]:
72
+ payload: dict[str, Any] = {}
73
+ for item in items:
74
+ if "=" not in item:
75
+ raise QingflowApiError.config_error("field assignments must use name=value format")
76
+ key, raw_value = item.split("=", 1)
77
+ name = key.strip()
78
+ if not name:
79
+ raise QingflowApiError.config_error("field name cannot be empty")
80
+ payload[name] = _coerce_value(raw_value)
81
+ return payload
82
+
83
+
84
+ def merge_object_inputs(args: argparse.Namespace, *, allow_empty: bool = False) -> dict[str, Any]:
85
+ payload = parse_field_assignments(list(getattr(args, "fields", []) or []))
86
+ if getattr(args, "stdin_json", False) or getattr(args, "file", None):
87
+ loaded = load_object_input(args, required=False)
88
+ payload.update(loaded)
89
+ if not payload and not allow_empty:
90
+ raise QingflowApiError.config_error("provide at least one --field or pass --stdin-json/--file")
91
+ return payload
92
+
93
+
94
+ def _read_stdin_json() -> Any:
95
+ raw = sys.stdin.read()
96
+ if not raw.strip():
97
+ raise QingflowApiError.config_error("stdin JSON is empty")
98
+ try:
99
+ return json.loads(raw)
100
+ except json.JSONDecodeError as error:
101
+ raise QingflowApiError.config_error(f"stdin JSON is invalid: {error.msg}") from error
102
+
103
+
104
+ def _coerce_value(raw: str) -> Any:
105
+ text = raw.strip()
106
+ if not text:
107
+ return ""
108
+ if text.startswith("{") or text.startswith("[") or text.startswith('"'):
109
+ try:
110
+ return json.loads(text)
111
+ except json.JSONDecodeError:
112
+ return raw
113
+ normalized = text.lower()
114
+ if normalized in {"true", "false", "null"}:
115
+ return json.loads(normalized)
116
+ try:
117
+ if "." in text:
118
+ return float(text)
119
+ return int(text)
120
+ except ValueError:
121
+ return raw