@qingflow-tech/qingflow-app-builder-mcp 1.0.1 → 1.0.3
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 +2 -2
- package/docs/local-agent-install.md +9 -3
- package/npm/lib/runtime.mjs +10 -3
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-builder/SKILL.md +88 -184
- package/skills/qingflow-app-builder/references/create-app.md +15 -34
- package/skills/qingflow-app-builder/references/gotchas.md +3 -3
- package/skills/qingflow-app-builder/references/solution-playbooks.md +1 -2
- package/skills/qingflow-app-builder/references/tool-selection.md +9 -10
- package/src/qingflow_mcp/__init__.py +33 -1
- package/src/qingflow_mcp/builder_facade/models.py +14 -4
- package/src/qingflow_mcp/builder_facade/service.py +1582 -124
- package/src/qingflow_mcp/cli/commands/auth.py +69 -1
- package/src/qingflow_mcp/cli/commands/builder.py +4 -3
- package/src/qingflow_mcp/cli/commands/record.py +5 -5
- package/src/qingflow_mcp/cli/commands/task.py +74 -22
- package/src/qingflow_mcp/cli/commands/workspace.py +22 -0
- package/src/qingflow_mcp/cli/formatters.py +287 -48
- package/src/qingflow_mcp/cli/main.py +6 -1
- package/src/qingflow_mcp/cli/qingflow_login.py +116 -0
- package/src/qingflow_mcp/config.py +8 -0
- package/src/qingflow_mcp/errors.py +2 -2
- package/src/qingflow_mcp/id_utils.py +49 -0
- package/src/qingflow_mcp/public_surface.py +11 -1
- package/src/qingflow_mcp/response_trim.py +380 -9
- package/src/qingflow_mcp/server.py +4 -0
- package/src/qingflow_mcp/server_app_builder.py +11 -1
- package/src/qingflow_mcp/server_app_user.py +24 -0
- package/src/qingflow_mcp/session_store.py +69 -15
- package/src/qingflow_mcp/solution/compiler/form_compiler.py +2 -2
- package/src/qingflow_mcp/solution/executor.py +2 -2
- package/src/qingflow_mcp/tools/ai_builder_tools.py +48 -18
- package/src/qingflow_mcp/tools/app_tools.py +1 -0
- package/src/qingflow_mcp/tools/auth_tools.py +271 -12
- package/src/qingflow_mcp/tools/base.py +6 -2
- package/src/qingflow_mcp/tools/code_block_tools.py +2 -2
- package/src/qingflow_mcp/tools/import_tools.py +36 -2
- package/src/qingflow_mcp/tools/record_tools.py +410 -156
- package/src/qingflow_mcp/tools/resource_read_tools.py +114 -32
- package/src/qingflow_mcp/tools/task_context_tools.py +899 -141
- package/src/qingflow_mcp/tools/workspace_tools.py +141 -0
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
|
+
import getpass
|
|
5
|
+
import sys
|
|
4
6
|
|
|
7
|
+
from ...errors import QingflowApiError
|
|
5
8
|
from ..context import CliContext
|
|
9
|
+
from ..qingflow_login import login_with_qingflow_password
|
|
6
10
|
from .common import read_secret_arg
|
|
7
11
|
|
|
8
12
|
|
|
@@ -10,6 +14,15 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
10
14
|
parser = subparsers.add_parser("auth", help="认证与会话")
|
|
11
15
|
auth_subparsers = parser.add_subparsers(dest="auth_command", required=True)
|
|
12
16
|
|
|
17
|
+
login = auth_subparsers.add_parser("login", help="登录 CLI:轻流账号密码交互")
|
|
18
|
+
login.add_argument("--base-url")
|
|
19
|
+
login.add_argument("--qf-version")
|
|
20
|
+
login.add_argument("--email", help="轻流账号邮箱;不传时在交互终端提示输入")
|
|
21
|
+
login.add_argument("--password", help="轻流账号密码;建议仅用于本地调试,脚本请优先使用 --password-stdin")
|
|
22
|
+
login.add_argument("--password-stdin", action="store_true", help="从标准输入读取轻流账号密码")
|
|
23
|
+
login.add_argument("--persist", action=argparse.BooleanOptionalAction, default=True)
|
|
24
|
+
login.set_defaults(handler=_handle_login, format_hint="auth_whoami")
|
|
25
|
+
|
|
13
26
|
use_credential = auth_subparsers.add_parser("use-credential", help="直接注入 credential")
|
|
14
27
|
use_credential.add_argument("--base-url")
|
|
15
28
|
use_credential.add_argument("--qf-version")
|
|
@@ -25,8 +38,63 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
25
38
|
logout.add_argument("--forget-persisted", action="store_true")
|
|
26
39
|
logout.set_defaults(handler=_handle_logout, format_hint="")
|
|
27
40
|
|
|
41
|
+
|
|
42
|
+
def _handle_login(args: argparse.Namespace, context: CliContext) -> dict:
|
|
43
|
+
email = _resolve_login_email(args)
|
|
44
|
+
password = _resolve_login_password(args)
|
|
45
|
+
login_result = login_with_qingflow_password(
|
|
46
|
+
base_url=args.base_url,
|
|
47
|
+
email=email,
|
|
48
|
+
password=password,
|
|
49
|
+
)
|
|
50
|
+
result = context.auth.auth_use_token(
|
|
51
|
+
profile=args.profile,
|
|
52
|
+
base_url=args.base_url,
|
|
53
|
+
qf_version=args.qf_version,
|
|
54
|
+
token=login_result.token,
|
|
55
|
+
login_token=login_result.login_token,
|
|
56
|
+
user_info=login_result.user_info,
|
|
57
|
+
persist=bool(args.persist),
|
|
58
|
+
)
|
|
59
|
+
warnings = list(result.get("warnings") or []) if isinstance(result, dict) else []
|
|
60
|
+
if isinstance(result, dict):
|
|
61
|
+
result["cli_auth"] = {"flow": login_result.flow}
|
|
62
|
+
if warnings:
|
|
63
|
+
result["warnings"] = warnings
|
|
64
|
+
return result
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _resolve_login_email(args: argparse.Namespace) -> str:
|
|
68
|
+
email = str(args.email or "").strip()
|
|
69
|
+
if email:
|
|
70
|
+
return email
|
|
71
|
+
if sys.stdin.isatty():
|
|
72
|
+
return input("Qingflow email: ").strip()
|
|
73
|
+
raise QingflowApiError.config_error(
|
|
74
|
+
"qingflow auth login needs an interactive terminal, or pass --email with --password-stdin."
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _resolve_login_password(args: argparse.Namespace) -> str:
|
|
79
|
+
if args.password or bool(args.password_stdin):
|
|
80
|
+
return read_secret_arg(
|
|
81
|
+
args.password,
|
|
82
|
+
stdin_enabled=bool(args.password_stdin),
|
|
83
|
+
label="password",
|
|
84
|
+
)
|
|
85
|
+
if sys.stdin.isatty():
|
|
86
|
+
return getpass.getpass("Qingflow password: ")
|
|
87
|
+
raise QingflowApiError.config_error(
|
|
88
|
+
"qingflow auth login needs an interactive terminal or --password-stdin."
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
28
92
|
def _handle_use_credential(args: argparse.Namespace, context: CliContext) -> dict:
|
|
29
|
-
credential =
|
|
93
|
+
credential = (
|
|
94
|
+
read_secret_arg(args.credential, stdin_enabled=bool(args.credential_stdin), label="credential")
|
|
95
|
+
if args.credential or bool(args.credential_stdin)
|
|
96
|
+
else ""
|
|
97
|
+
)
|
|
30
98
|
return context.auth.auth_use_credential(
|
|
31
99
|
profile=args.profile,
|
|
32
100
|
base_url=args.base_url,
|
|
@@ -101,7 +101,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
101
101
|
app_release_lock.add_argument("--lock-owner-name", required=True)
|
|
102
102
|
app_release_lock.set_defaults(handler=_handle_app_release_edit_lock_if_mine, format_hint="builder_summary")
|
|
103
103
|
|
|
104
|
-
app_get = app_subparsers.add_parser("get", help="
|
|
104
|
+
app_get = app_subparsers.add_parser("get", help="读取应用配置(字段请使用: builder app get --app-key APP fields)")
|
|
105
105
|
app_get.add_argument(
|
|
106
106
|
"builder_app_get_section",
|
|
107
107
|
nargs="?",
|
|
@@ -160,7 +160,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
160
160
|
portal_apply.add_argument("--dash-name", default="")
|
|
161
161
|
portal_apply.add_argument("--package-id", type=int)
|
|
162
162
|
portal_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
|
|
163
|
-
portal_apply.add_argument("--sections-file"
|
|
163
|
+
portal_apply.add_argument("--sections-file")
|
|
164
164
|
portal_apply.add_argument("--visibility-file")
|
|
165
165
|
portal_apply.add_argument("--auth-file")
|
|
166
166
|
portal_apply.add_argument("--icon")
|
|
@@ -513,13 +513,14 @@ def _handle_portal_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
513
513
|
"portal apply requires either --dash-key, or --package-id together with --dash-name.",
|
|
514
514
|
fix_hint="Use `--dash-key` for an existing portal. For create mode, pass `--package-id --dash-name`.",
|
|
515
515
|
)
|
|
516
|
+
sections = [] if not args.sections_file else require_list_arg(args.sections_file, option_name="--sections-file")
|
|
516
517
|
return context.builder.portal_apply(
|
|
517
518
|
profile=args.profile,
|
|
518
519
|
dash_key=args.dash_key,
|
|
519
520
|
dash_name=args.dash_name,
|
|
520
521
|
package_id=args.package_id,
|
|
521
522
|
publish=bool(args.publish),
|
|
522
|
-
sections=
|
|
523
|
+
sections=sections,
|
|
523
524
|
visibility=load_object_arg(args.visibility_file, option_name="--visibility-file"),
|
|
524
525
|
auth=load_object_arg(args.auth_file, option_name="--auth-file"),
|
|
525
526
|
icon=args.icon,
|
|
@@ -32,7 +32,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
32
32
|
|
|
33
33
|
schema_update = schema_subparsers.add_parser("update", help="读取更新记录表结构")
|
|
34
34
|
schema_update.add_argument("--app-key", required=True)
|
|
35
|
-
schema_update.add_argument("--record-id", required=True
|
|
35
|
+
schema_update.add_argument("--record-id", required=True)
|
|
36
36
|
schema_update.set_defaults(handler=_handle_schema_update, format_hint="")
|
|
37
37
|
|
|
38
38
|
schema_import = schema_subparsers.add_parser("import", help="读取导入表结构")
|
|
@@ -59,7 +59,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
59
59
|
|
|
60
60
|
get = record_subparsers.add_parser("get", help="读取单条记录")
|
|
61
61
|
get.add_argument("--app-key", required=True)
|
|
62
|
-
get.add_argument("--record-id", required=True
|
|
62
|
+
get.add_argument("--record-id", required=True)
|
|
63
63
|
get.add_argument("--column", dest="columns", action="append", type=int, default=[])
|
|
64
64
|
get.add_argument("--columns-file")
|
|
65
65
|
get.add_argument("--view-id")
|
|
@@ -73,7 +73,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
73
73
|
|
|
74
74
|
update = record_subparsers.add_parser("update", help="更新记录")
|
|
75
75
|
update.add_argument("--app-key", required=True)
|
|
76
|
-
update.add_argument("--record-id"
|
|
76
|
+
update.add_argument("--record-id")
|
|
77
77
|
update.add_argument("--fields-file")
|
|
78
78
|
update.add_argument("--items-file")
|
|
79
79
|
update.add_argument("--dry-run", action=argparse.BooleanOptionalAction, default=False)
|
|
@@ -82,7 +82,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
82
82
|
|
|
83
83
|
delete = record_subparsers.add_parser("delete", help="删除记录")
|
|
84
84
|
delete.add_argument("--app-key", required=True)
|
|
85
|
-
delete.add_argument("--record-id"
|
|
85
|
+
delete.add_argument("--record-id")
|
|
86
86
|
delete.add_argument("--record-ids-file")
|
|
87
87
|
delete.set_defaults(handler=_handle_delete, format_hint="")
|
|
88
88
|
|
|
@@ -102,7 +102,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
102
102
|
|
|
103
103
|
code_block = record_subparsers.add_parser("code-block-run", help="执行代码块字段")
|
|
104
104
|
code_block.add_argument("--app-key", required=True)
|
|
105
|
-
code_block.add_argument("--record-id", required=True
|
|
105
|
+
code_block.add_argument("--record-id", required=True)
|
|
106
106
|
code_block.add_argument("--code-block-field", required=True)
|
|
107
107
|
code_block.add_argument("--role", type=int, default=1)
|
|
108
108
|
code_block.add_argument("--workflow-node-id", type=int)
|
|
@@ -15,32 +15,52 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
15
15
|
list_parser.add_argument("--flow-status", default="all")
|
|
16
16
|
list_parser.add_argument("--app-key")
|
|
17
17
|
list_parser.add_argument("--workflow-node-id", type=int)
|
|
18
|
-
list_parser.add_argument(
|
|
18
|
+
list_parser.add_argument(
|
|
19
|
+
"--query",
|
|
20
|
+
help="先走后端待办检索;当后端返回零结果时,公开 task_list 会回退到本地匹配 app_name / workflow_node_name / app_key / record_id。",
|
|
21
|
+
)
|
|
19
22
|
list_parser.add_argument("--page", type=int, default=1)
|
|
20
23
|
list_parser.add_argument("--page-size", type=int, default=20)
|
|
21
24
|
list_parser.set_defaults(handler=_handle_list, format_hint="task_list")
|
|
22
25
|
|
|
23
|
-
get = task_subparsers.add_parser("get", help="
|
|
24
|
-
get.add_argument("--
|
|
25
|
-
get.add_argument("--
|
|
26
|
-
get.add_argument("--
|
|
26
|
+
get = task_subparsers.add_parser("get", help="读取待办详情;推荐直接传 --task-id")
|
|
27
|
+
get.add_argument("--task-id")
|
|
28
|
+
get.add_argument("--app-key")
|
|
29
|
+
get.add_argument("--record-id")
|
|
30
|
+
get.add_argument("--workflow-node-id", type=int)
|
|
27
31
|
get.add_argument("--include-candidates", action=argparse.BooleanOptionalAction, default=True)
|
|
28
32
|
get.add_argument("--include-associated-reports", action=argparse.BooleanOptionalAction, default=True)
|
|
29
33
|
get.set_defaults(handler=_handle_get, format_hint="task_get")
|
|
30
34
|
|
|
31
35
|
action = task_subparsers.add_parser("action", help="执行待办动作")
|
|
32
|
-
action.add_argument("--
|
|
33
|
-
action.add_argument("--
|
|
34
|
-
action.add_argument("--
|
|
36
|
+
action.add_argument("--task-id")
|
|
37
|
+
action.add_argument("--app-key")
|
|
38
|
+
action.add_argument("--record-id")
|
|
39
|
+
action.add_argument("--workflow-node-id", type=int)
|
|
35
40
|
action.add_argument("--action", required=True)
|
|
36
41
|
action.add_argument("--payload-file")
|
|
37
42
|
action.add_argument("--fields-file")
|
|
38
|
-
action.set_defaults(
|
|
43
|
+
action.set_defaults(
|
|
44
|
+
handler=_handle_action,
|
|
45
|
+
format_hint="task_action_execute",
|
|
46
|
+
hide_effective_context_line=True,
|
|
47
|
+
)
|
|
39
48
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
report = task_subparsers.add_parser("report", help="读取待办关联报表详情;推荐直接传 --task-id")
|
|
50
|
+
report.add_argument("--task-id")
|
|
51
|
+
report.add_argument("--app-key")
|
|
52
|
+
report.add_argument("--record-id")
|
|
53
|
+
report.add_argument("--workflow-node-id", type=int)
|
|
54
|
+
report.add_argument("--report-id", required=True, type=int)
|
|
55
|
+
report.add_argument("--page", type=int, default=1)
|
|
56
|
+
report.add_argument("--page-size", type=int, default=20)
|
|
57
|
+
report.set_defaults(handler=_handle_report, format_hint="task_associated_report_detail_get")
|
|
58
|
+
|
|
59
|
+
log = task_subparsers.add_parser("log", help="读取流程日志;推荐直接传 --task-id")
|
|
60
|
+
log.add_argument("--task-id")
|
|
61
|
+
log.add_argument("--app-key")
|
|
62
|
+
log.add_argument("--record-id")
|
|
63
|
+
log.add_argument("--workflow-node-id", type=int)
|
|
44
64
|
log.set_defaults(handler=_handle_log, format_hint="")
|
|
45
65
|
|
|
46
66
|
|
|
@@ -58,22 +78,32 @@ def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
58
78
|
|
|
59
79
|
|
|
60
80
|
def _handle_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
81
|
+
if not args.task_id and not (args.app_key and args.record_id and args.workflow_node_id):
|
|
82
|
+
raise RuntimeError(
|
|
83
|
+
'{"category":"config","message":"task get requires --task-id, or --app-key together with --record-id and --workflow-node-id"}'
|
|
84
|
+
)
|
|
61
85
|
return context.task.task_get(
|
|
62
86
|
profile=args.profile,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
87
|
+
task_id=args.task_id,
|
|
88
|
+
app_key=args.app_key or "",
|
|
89
|
+
record_id=args.record_id or "",
|
|
90
|
+
workflow_node_id=int(args.workflow_node_id or 0),
|
|
66
91
|
include_candidates=bool(args.include_candidates),
|
|
67
92
|
include_associated_reports=bool(args.include_associated_reports),
|
|
68
93
|
)
|
|
69
94
|
|
|
70
95
|
|
|
71
96
|
def _handle_action(args: argparse.Namespace, context: CliContext) -> dict:
|
|
97
|
+
if not args.task_id and not (args.app_key and args.record_id and args.workflow_node_id):
|
|
98
|
+
raise RuntimeError(
|
|
99
|
+
'{"category":"config","message":"task action requires --task-id, or --app-key together with --record-id and --workflow-node-id"}'
|
|
100
|
+
)
|
|
72
101
|
return context.task.task_action_execute(
|
|
73
102
|
profile=args.profile,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
103
|
+
task_id=args.task_id,
|
|
104
|
+
app_key=args.app_key or "",
|
|
105
|
+
record_id=args.record_id or "",
|
|
106
|
+
workflow_node_id=int(args.workflow_node_id or 0),
|
|
77
107
|
action=args.action,
|
|
78
108
|
payload=load_object_arg(args.payload_file, option_name="--payload-file") or {},
|
|
79
109
|
fields=load_object_arg(args.fields_file, option_name="--fields-file") or {},
|
|
@@ -81,9 +111,31 @@ def _handle_action(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
81
111
|
|
|
82
112
|
|
|
83
113
|
def _handle_log(args: argparse.Namespace, context: CliContext) -> dict:
|
|
114
|
+
if not args.task_id and not (args.app_key and args.record_id and args.workflow_node_id):
|
|
115
|
+
raise RuntimeError(
|
|
116
|
+
'{"category":"config","message":"task log requires --task-id, or --app-key together with --record-id and --workflow-node-id"}'
|
|
117
|
+
)
|
|
84
118
|
return context.task.task_workflow_log_get(
|
|
85
119
|
profile=args.profile,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
120
|
+
task_id=args.task_id,
|
|
121
|
+
app_key=args.app_key or "",
|
|
122
|
+
record_id=args.record_id or "",
|
|
123
|
+
workflow_node_id=int(args.workflow_node_id or 0),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _handle_report(args: argparse.Namespace, context: CliContext) -> dict:
|
|
128
|
+
if not args.task_id and not (args.app_key and args.record_id and args.workflow_node_id):
|
|
129
|
+
raise RuntimeError(
|
|
130
|
+
'{"category":"config","message":"task report requires --task-id, or --app-key together with --record-id and --workflow-node-id"}'
|
|
131
|
+
)
|
|
132
|
+
return context.task.task_associated_report_detail_get(
|
|
133
|
+
profile=args.profile,
|
|
134
|
+
task_id=args.task_id,
|
|
135
|
+
app_key=args.app_key or "",
|
|
136
|
+
record_id=args.record_id or "",
|
|
137
|
+
workflow_node_id=int(args.workflow_node_id or 0),
|
|
138
|
+
report_id=int(args.report_id),
|
|
139
|
+
page=int(args.page),
|
|
140
|
+
page_size=int(args.page_size),
|
|
89
141
|
)
|
|
@@ -15,6 +15,14 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
15
15
|
list_parser.add_argument("--include-external", action="store_true")
|
|
16
16
|
list_parser.set_defaults(handler=_handle_list, format_hint="workspace_list")
|
|
17
17
|
|
|
18
|
+
get_parser = workspace_subparsers.add_parser("get", help="读取工作区详情")
|
|
19
|
+
get_parser.add_argument("--ws-id", type=int, default=0)
|
|
20
|
+
get_parser.set_defaults(handler=_handle_get, format_hint="workspace_get")
|
|
21
|
+
|
|
22
|
+
select_parser = workspace_subparsers.add_parser("select", help="切换当前工作区")
|
|
23
|
+
select_parser.add_argument("--ws-id", type=int, required=True)
|
|
24
|
+
select_parser.set_defaults(handler=_handle_select, format_hint="workspace_get")
|
|
25
|
+
|
|
18
26
|
|
|
19
27
|
def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
|
|
20
28
|
return context.workspace.workspace_list(
|
|
@@ -23,3 +31,17 @@ def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
23
31
|
page_size=args.page_size,
|
|
24
32
|
include_external=bool(args.include_external),
|
|
25
33
|
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _handle_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
37
|
+
return context.workspace.workspace_get(
|
|
38
|
+
profile=args.profile,
|
|
39
|
+
ws_id=args.ws_id if int(args.ws_id or 0) > 0 else None,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _handle_select(args: argparse.Namespace, context: CliContext) -> dict:
|
|
44
|
+
return context.workspace.workspace_select(
|
|
45
|
+
profile=args.profile,
|
|
46
|
+
ws_id=int(args.ws_id),
|
|
47
|
+
)
|