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

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.
@@ -3,52 +3,53 @@ from __future__ import annotations
3
3
  import argparse
4
4
 
5
5
  from ..context import CliContext
6
- from .common import load_object_arg
6
+ from .common import add_file_arg, add_stdin_json_flag, load_object_input
7
7
 
8
8
 
9
9
  def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
10
- parser = subparsers.add_parser("task", help="待办与流程上下文")
11
- task_subparsers = parser.add_subparsers(dest="task_command", required=True)
10
+ parser = subparsers.add_parser("tasks", help="待办与流程上下文")
11
+ task_subparsers = parser.add_subparsers(dest="tasks_command", required=True)
12
12
 
13
13
  list_parser = task_subparsers.add_parser("list", help="列出待办")
14
14
  list_parser.add_argument("--task-box", default="todo")
15
15
  list_parser.add_argument("--flow-status", default="all")
16
- list_parser.add_argument("--app-key")
17
- list_parser.add_argument("--workflow-node-id", type=int)
16
+ list_parser.add_argument("--app")
17
+ list_parser.add_argument("--node", dest="workflow_node_id", type=int)
18
18
  list_parser.add_argument("--query")
19
19
  list_parser.add_argument("--page", type=int, default=1)
20
20
  list_parser.add_argument("--page-size", type=int, default=20)
21
- list_parser.set_defaults(handler=_handle_list, format_hint="task_list")
22
-
23
- get = task_subparsers.add_parser("get", help="读取待办详情")
24
- get.add_argument("--app-key", required=True)
25
- get.add_argument("--record-id", required=True, type=int)
26
- get.add_argument("--workflow-node-id", required=True, type=int)
27
- get.add_argument("--include-candidates", action=argparse.BooleanOptionalAction, default=True)
28
- get.add_argument("--include-associated-reports", action=argparse.BooleanOptionalAction, default=True)
29
- get.set_defaults(handler=_handle_get, format_hint="")
30
-
31
- action = task_subparsers.add_parser("action", help="执行待办动作")
32
- action.add_argument("--app-key", required=True)
33
- action.add_argument("--record-id", required=True, type=int)
34
- action.add_argument("--workflow-node-id", required=True, type=int)
35
- action.add_argument("--action", required=True)
36
- action.add_argument("--payload-file")
37
- action.set_defaults(handler=_handle_action, format_hint="")
21
+ list_parser.set_defaults(handler=_handle_list, format_hint="tasks_list")
22
+
23
+ show = task_subparsers.add_parser("show", help="读取待办详情")
24
+ show.add_argument("--app", required=True)
25
+ show.add_argument("--record", required=True, type=int)
26
+ show.add_argument("--node", required=True, type=int)
27
+ show.add_argument("--include-candidates", action=argparse.BooleanOptionalAction, default=True)
28
+ show.add_argument("--include-associated-reports", action=argparse.BooleanOptionalAction, default=True)
29
+ show.set_defaults(handler=_handle_show, format_hint="task_show")
30
+
31
+ act = task_subparsers.add_parser("act", help="执行待办动作")
32
+ act.add_argument("--app", required=True)
33
+ act.add_argument("--record", required=True, type=int)
34
+ act.add_argument("--node", required=True, type=int)
35
+ act.add_argument("--action", required=True)
36
+ add_file_arg(act)
37
+ add_stdin_json_flag(act)
38
+ act.set_defaults(handler=_handle_act, format_hint="task_action")
38
39
 
39
40
  log = task_subparsers.add_parser("log", help="读取流程日志")
40
- log.add_argument("--app-key", required=True)
41
- log.add_argument("--record-id", required=True, type=int)
42
- log.add_argument("--workflow-node-id", required=True, type=int)
43
- log.set_defaults(handler=_handle_log, format_hint="")
41
+ log.add_argument("--app", required=True)
42
+ log.add_argument("--record", required=True, type=int)
43
+ log.add_argument("--node", required=True, type=int)
44
+ log.set_defaults(handler=_handle_log, format_hint="task_log")
44
45
 
45
46
 
46
47
  def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
47
- return context.task.task_list(
48
+ return context.tasks.list(
48
49
  profile=args.profile,
49
50
  task_box=args.task_box,
50
51
  flow_status=args.flow_status,
51
- app_key=args.app_key,
52
+ app_key=args.app,
52
53
  workflow_node_id=args.workflow_node_id,
53
54
  query=args.query,
54
55
  page=args.page,
@@ -56,32 +57,32 @@ def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
56
57
  )
57
58
 
58
59
 
59
- def _handle_get(args: argparse.Namespace, context: CliContext) -> dict:
60
- return context.task.task_get(
60
+ def _handle_show(args: argparse.Namespace, context: CliContext) -> dict:
61
+ return context.tasks.show(
61
62
  profile=args.profile,
62
- app_key=args.app_key,
63
- record_id=args.record_id,
64
- workflow_node_id=args.workflow_node_id,
63
+ app_key=args.app,
64
+ record_id=args.record,
65
+ workflow_node_id=args.node,
65
66
  include_candidates=bool(args.include_candidates),
66
67
  include_associated_reports=bool(args.include_associated_reports),
67
68
  )
68
69
 
69
70
 
70
- def _handle_action(args: argparse.Namespace, context: CliContext) -> dict:
71
- return context.task.task_action_execute(
71
+ def _handle_act(args: argparse.Namespace, context: CliContext) -> dict:
72
+ return context.tasks.act(
72
73
  profile=args.profile,
73
- app_key=args.app_key,
74
- record_id=args.record_id,
75
- workflow_node_id=args.workflow_node_id,
74
+ app_key=args.app,
75
+ record_id=args.record,
76
+ workflow_node_id=args.node,
76
77
  action=args.action,
77
- payload=load_object_arg(args.payload_file, option_name="--payload-file") or {},
78
+ payload=load_object_input(args, required=False),
78
79
  )
79
80
 
80
81
 
81
82
  def _handle_log(args: argparse.Namespace, context: CliContext) -> dict:
82
- return context.task.task_workflow_log_get(
83
+ return context.tasks.log(
83
84
  profile=args.profile,
84
- app_key=args.app_key,
85
- record_id=args.record_id,
86
- workflow_node_id=args.workflow_node_id,
85
+ app_key=args.app,
86
+ record_id=args.record,
87
+ workflow_node_id=args.node,
87
88
  )
@@ -6,22 +6,22 @@ from ..context import CliContext
6
6
 
7
7
 
8
8
  def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
9
- parser = subparsers.add_parser("workspace", help="工作区")
10
- workspace_subparsers = parser.add_subparsers(dest="workspace_command", required=True)
9
+ parser = subparsers.add_parser("ws", help="工作区操作")
10
+ ws_subparsers = parser.add_subparsers(dest="ws_command", required=True)
11
11
 
12
- list_parser = workspace_subparsers.add_parser("list", help="列出工作区")
12
+ list_parser = ws_subparsers.add_parser("list", help="列出工作区")
13
13
  list_parser.add_argument("--page", type=int, default=1)
14
14
  list_parser.add_argument("--page-size", type=int, default=20)
15
15
  list_parser.add_argument("--include-external", action="store_true")
16
- list_parser.set_defaults(handler=_handle_list, format_hint="workspace_list")
16
+ list_parser.set_defaults(handler=_handle_list, format_hint="ws_list")
17
17
 
18
- select = workspace_subparsers.add_parser("select", help="切换工作区")
19
- select.add_argument("--ws-id", type=int, required=True)
20
- select.set_defaults(handler=_handle_select, format_hint="")
18
+ use = ws_subparsers.add_parser("use", help="切换工作区")
19
+ use.add_argument("--ws-id", type=int, required=True)
20
+ use.set_defaults(handler=_handle_use, format_hint="ws_use")
21
21
 
22
22
 
23
23
  def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
24
- return context.workspace.workspace_list(
24
+ return context.workspace.list(
25
25
  profile=args.profile,
26
26
  page_num=args.page,
27
27
  page_size=args.page_size,
@@ -29,5 +29,5 @@ def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
29
29
  )
30
30
 
31
31
 
32
- def _handle_select(args: argparse.Namespace, context: CliContext) -> dict:
33
- return context.workspace.workspace_select(profile=args.profile, ws_id=args.ws_id)
32
+ def _handle_use(args: argparse.Namespace, context: CliContext) -> dict:
33
+ return context.workspace.use(profile=args.profile, ws_id=args.ws_id)
@@ -2,47 +2,44 @@ from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
4
 
5
- from ..backend_client import BackendClient
6
- from ..session_store import SessionStore
7
- from ..tools.ai_builder_tools import AiBuilderTools
8
- from ..tools.app_tools import AppTools
9
- from ..tools.auth_tools import AuthTools
10
- from ..tools.code_block_tools import CodeBlockTools
11
- from ..tools.import_tools import ImportTools
12
- from ..tools.record_tools import RecordTools
13
- from ..tools.task_context_tools import TaskContextTools
14
- from ..tools.workspace_tools import WorkspaceTools
5
+ from ..ops.context import OperationsRuntime, build_operations_runtime
15
6
 
16
7
 
17
8
  @dataclass(slots=True)
18
9
  class CliContext:
19
- sessions: SessionStore
20
- backend: BackendClient
21
- auth: AuthTools
22
- workspace: WorkspaceTools
23
- app: AppTools
24
- record: RecordTools
25
- code_block: CodeBlockTools
26
- imports: ImportTools
27
- task: TaskContextTools
28
- builder: AiBuilderTools
10
+ runtime: OperationsRuntime
11
+
12
+ @property
13
+ def auth(self):
14
+ return self.runtime.auth
15
+
16
+ @property
17
+ def workspace(self):
18
+ return self.runtime.workspace
19
+
20
+ @property
21
+ def apps(self):
22
+ return self.runtime.apps
23
+
24
+ @property
25
+ def records(self):
26
+ return self.runtime.records
27
+
28
+ @property
29
+ def imports(self):
30
+ return self.runtime.imports
31
+
32
+ @property
33
+ def tasks(self):
34
+ return self.runtime.tasks
35
+
36
+ @property
37
+ def build(self):
38
+ return self.runtime.builder
29
39
 
30
40
  def close(self) -> None:
31
- self.backend.close()
41
+ self.runtime.close()
32
42
 
33
43
 
34
44
  def build_cli_context() -> CliContext:
35
- sessions = SessionStore()
36
- backend = BackendClient()
37
- return CliContext(
38
- sessions=sessions,
39
- backend=backend,
40
- auth=AuthTools(sessions, backend),
41
- workspace=WorkspaceTools(sessions, backend),
42
- app=AppTools(sessions, backend),
43
- record=RecordTools(sessions, backend),
44
- code_block=CodeBlockTools(sessions, backend),
45
- imports=ImportTools(sessions, backend),
46
- task=TaskContextTools(sessions, backend),
47
- builder=AiBuilderTools(sessions, backend),
48
- )
45
+ return CliContext(runtime=build_operations_runtime())
@@ -3,8 +3,11 @@ from __future__ import annotations
3
3
  import json
4
4
  from typing import Any, TextIO
5
5
 
6
+ from ..ops.base import public_result
7
+
6
8
 
7
9
  def emit_text_result(result: dict[str, Any], *, hint: str, stream: TextIO) -> None:
10
+ result = public_result(result)
8
11
  formatter = _FORMATTERS.get(hint, _format_generic)
9
12
  text = formatter(result)
10
13
  stream.write(text)
@@ -12,61 +15,65 @@ def emit_text_result(result: dict[str, Any], *, hint: str, stream: TextIO) -> No
12
15
  stream.write("\n")
13
16
 
14
17
 
18
+ def emit_json_result(result: dict[str, Any], *, stream: TextIO) -> None:
19
+ json.dump(public_result(result), stream, ensure_ascii=False, indent=2)
20
+ stream.write("\n")
21
+
22
+
15
23
  def _format_generic(result: dict[str, Any]) -> str:
16
- lines: list[str] = []
17
- title = _first_present(result, "status", "message")
18
- if title:
19
- lines.append(str(title))
24
+ lines = [f"{result.get('message') or result.get('code') or '完成'}"]
20
25
  data = result.get("data")
21
26
  if isinstance(data, dict):
22
- scalar_lines = _dict_scalar_lines(data)
23
- if scalar_lines:
24
- lines.extend(scalar_lines)
25
- elif result:
26
- scalar_lines = _dict_scalar_lines(result)
27
- if scalar_lines:
28
- lines.extend(scalar_lines)
29
- if not lines:
30
- lines.append(json.dumps(result, ensure_ascii=False, indent=2))
27
+ lines.extend(_scalar_lines(data, limit=10))
31
28
  _append_warnings(lines, result.get("warnings"))
32
- _append_verification(lines, result.get("verification"))
33
29
  return "\n".join(lines) + "\n"
34
30
 
35
31
 
36
- def _format_whoami(result: dict[str, Any]) -> str:
32
+ def _format_me(result: dict[str, Any]) -> str:
33
+ data = result.get("data") if isinstance(result.get("data"), dict) else {}
34
+ user = data.get("user") if isinstance(data.get("user"), dict) else {}
35
+ workspace = data.get("workspace") if isinstance(data.get("workspace"), dict) else {}
37
36
  lines = [
38
- f"Profile: {result.get('profile')}",
39
- f"User: {result.get('nick_name') or '-'} ({result.get('email') or '-'})",
40
- f"UID: {result.get('uid')}",
41
- f"Workspace: {result.get('selected_ws_name') or '-'} ({result.get('selected_ws_id') or '-'})",
42
- f"Base URL: {result.get('base_url')}",
37
+ f"Profile: {data.get('profile') or result.get('meta', {}).get('profile') or '-'}",
38
+ f"User: {user.get('nick_name') or '-'} ({user.get('email') or '-'})",
39
+ f"UID: {user.get('uid') or '-'}",
40
+ f"Workspace: {workspace.get('name') or '-'} ({workspace.get('ws_id') or '-'})",
41
+ f"Base URL: {data.get('base_url') or '-'}",
43
42
  ]
44
43
  return "\n".join(lines) + "\n"
45
44
 
46
45
 
47
46
  def _format_workspace_list(result: dict[str, Any]) -> str:
48
- page = result.get("page") if isinstance(result.get("page"), dict) else {}
49
- items = page.get("list") if isinstance(page.get("list"), list) else []
50
- rows = []
51
- for item in items:
52
- if not isinstance(item, dict):
53
- continue
54
- rows.append(
55
- [
56
- str(item.get("wsId") or ""),
57
- str(item.get("workspaceName") or item.get("wsName") or ""),
58
- str(item.get("remark") or ""),
59
- ]
60
- )
47
+ data = result.get("data") if isinstance(result.get("data"), dict) else {}
48
+ items = data.get("items") if isinstance(data.get("items"), list) else []
49
+ rows = [
50
+ [
51
+ str(item.get("ws_id") or ""),
52
+ str(item.get("name") or ""),
53
+ str(item.get("remark") or ""),
54
+ ]
55
+ for item in items
56
+ if isinstance(item, dict)
57
+ ]
61
58
  return _render_titled_table("Workspaces", ["ws_id", "name", "remark"], rows)
62
59
 
63
60
 
64
- def _format_app_items(result: dict[str, Any]) -> str:
65
- items = result.get("items")
66
- if not isinstance(items, list):
67
- items = result.get("apps")
61
+ def _format_workspace_use(result: dict[str, Any]) -> str:
62
+ workspace = result.get("data", {}).get("workspace") if isinstance(result.get("data"), dict) else {}
63
+ lines = [
64
+ f"Workspace: {workspace.get('name') or '-'}",
65
+ f"WS ID: {workspace.get('ws_id') or '-'}",
66
+ f"QF Version: {result.get('data', {}).get('qf_version') or '-'}",
67
+ f"QF Version Source: {result.get('data', {}).get('qf_version_source') or '-'}",
68
+ ]
69
+ return "\n".join(lines) + "\n"
70
+
71
+
72
+ def _format_apps_list(result: dict[str, Any]) -> str:
73
+ data = result.get("data") if isinstance(result.get("data"), dict) else {}
74
+ items = data.get("items") if isinstance(data.get("items"), list) else []
68
75
  rows = []
69
- for item in items or []:
76
+ for item in items:
70
77
  if not isinstance(item, dict):
71
78
  continue
72
79
  rows.append(
@@ -79,7 +86,7 @@ def _format_app_items(result: dict[str, Any]) -> str:
79
86
  return _render_titled_table("Apps", ["app_key", "app_name", "package"], rows)
80
87
 
81
88
 
82
- def _format_app_get(result: dict[str, Any]) -> str:
89
+ def _format_app_show(result: dict[str, Any]) -> str:
83
90
  data = result.get("data") if isinstance(result.get("data"), dict) else {}
84
91
  lines = [
85
92
  f"App: {data.get('app_name') or '-'}",
@@ -90,8 +97,7 @@ def _format_app_get(result: dict[str, Any]) -> str:
90
97
  if isinstance(import_capability, dict):
91
98
  lines.append(
92
99
  "Import Capability: "
93
- f"{import_capability.get('auth_source') or 'unknown'} / "
94
- f"can_import={import_capability.get('can_import')}"
100
+ f"{import_capability.get('auth_source') or 'unknown'} / can_import={import_capability.get('can_import')}"
95
101
  )
96
102
  views = data.get("accessible_views") if isinstance(data.get("accessible_views"), list) else []
97
103
  lines.append(f"Accessible Views: {len(views)}")
@@ -102,7 +108,7 @@ def _format_app_get(result: dict[str, Any]) -> str:
102
108
  return "\n".join(lines) + "\n"
103
109
 
104
110
 
105
- def _format_record_list(result: dict[str, Any]) -> str:
111
+ def _format_records_list(result: dict[str, Any]) -> str:
106
112
  data = result.get("data") if isinstance(result.get("data"), dict) else {}
107
113
  items = data.get("items") if isinstance(data.get("items"), list) else []
108
114
  lines = [f"Returned Records: {len(items)}"]
@@ -112,40 +118,43 @@ def _format_record_list(result: dict[str, Any]) -> str:
112
118
  if len(items) > 10:
113
119
  lines.append(f"... {len(items) - 10} more")
114
120
  _append_warnings(lines, result.get("warnings"))
115
- _append_verification(lines, result.get("verification"))
116
121
  return "\n".join(lines) + "\n"
117
122
 
118
123
 
119
- def _format_task_list(result: dict[str, Any]) -> str:
124
+ def _format_record_show(result: dict[str, Any]) -> str:
120
125
  data = result.get("data") if isinstance(result.get("data"), dict) else {}
121
- items = data.get("items") if isinstance(data.get("items"), list) else []
122
- rows = []
123
- for item in items:
124
- if not isinstance(item, dict):
125
- continue
126
- rows.append(
127
- [
128
- str(item.get("app_key") or ""),
129
- str(item.get("record_id") or ""),
130
- str(item.get("workflow_node_id") or ""),
131
- str(item.get("title") or item.get("task_name") or ""),
132
- ]
133
- )
134
- output = _render_titled_table("Tasks", ["app_key", "record_id", "node_id", "title"], rows)
135
- lines = output.rstrip("\n").split("\n")
126
+ record = data.get("record")
127
+ if isinstance(record, dict):
128
+ lines = [f"Record ID: {data.get('record_id') or '-'}"]
129
+ lines.extend(_scalar_lines(record, limit=12))
130
+ _append_warnings(lines, result.get("warnings"))
131
+ return "\n".join(lines) + "\n"
132
+ return _format_generic(result)
133
+
134
+
135
+ def _format_record_write(result: dict[str, Any]) -> str:
136
+ data = result.get("data") if isinstance(result.get("data"), dict) else {}
137
+ lines = [
138
+ f"Code: {result.get('code')}",
139
+ f"Message: {result.get('message')}",
140
+ ]
141
+ if data.get("record_id") is not None:
142
+ lines.append(f"Record ID: {data.get('record_id')}")
143
+ if isinstance(data.get("record_ids"), list):
144
+ lines.append(f"Record IDs: {', '.join(str(item) for item in data['record_ids'])}")
136
145
  _append_warnings(lines, result.get("warnings"))
137
146
  return "\n".join(lines) + "\n"
138
147
 
139
148
 
140
149
  def _format_import_verify(result: dict[str, Any]) -> str:
150
+ data = result.get("data") if isinstance(result.get("data"), dict) else {}
141
151
  lines = [
142
- f"App Key: {result.get('app_key') or '-'}",
143
- f"File: {result.get('file_name') or result.get('file_path') or '-'}",
144
- f"Can Import: {result.get('can_import')}",
145
- f"Apply Rows: {result.get('apply_rows')}",
146
- f"Verification ID: {result.get('verification_id') or '-'}",
152
+ f"File: {data.get('file_name') or data.get('file_path') or '-'}",
153
+ f"Can Import: {data.get('can_import')}",
154
+ f"Apply Rows: {data.get('apply_rows')}",
155
+ f"Verification ID: {data.get('verification_id') or '-'}",
147
156
  ]
148
- issues = result.get("issues") if isinstance(result.get("issues"), list) else []
157
+ issues = data.get("issues") if isinstance(data.get("issues"), list) else []
149
158
  if issues:
150
159
  lines.append("Issues:")
151
160
  for issue in issues:
@@ -154,48 +163,51 @@ def _format_import_verify(result: dict[str, Any]) -> str:
154
163
  else:
155
164
  lines.append(f"- {issue}")
156
165
  _append_warnings(lines, result.get("warnings"))
157
- _append_verification(lines, result.get("verification"))
158
166
  return "\n".join(lines) + "\n"
159
167
 
160
168
 
161
169
  def _format_import_status(result: dict[str, Any]) -> str:
170
+ data = result.get("data") if isinstance(result.get("data"), dict) else {}
162
171
  lines = [
163
- f"Status: {result.get('status') or '-'}",
164
- f"Import ID: {result.get('import_id') or '-'}",
165
- f"Process ID: {result.get('process_id_str') or '-'}",
166
- f"Success Rows: {result.get('success_rows') or 0}",
167
- f"Failed Rows: {result.get('failed_rows') or 0}",
168
- f"Progress: {result.get('progress') or '-'}",
172
+ f"Status: {data.get('status') or '-'}",
173
+ f"Import ID: {data.get('import_id') or '-'}",
174
+ f"Process ID: {data.get('process_id_str') or '-'}",
175
+ f"Success Rows: {data.get('success_rows') or 0}",
176
+ f"Failed Rows: {data.get('failed_rows') or 0}",
177
+ f"Progress: {data.get('progress') or '-'}",
169
178
  ]
170
179
  _append_warnings(lines, result.get("warnings"))
171
- _append_verification(lines, result.get("verification"))
172
180
  return "\n".join(lines) + "\n"
173
181
 
174
182
 
175
- def _format_builder_summary(result: dict[str, Any]) -> str:
176
- lines = []
177
- if "status" in result:
178
- lines.append(f"Status: {result.get('status')}")
179
- if "app_key" in result:
180
- lines.append(f"App Key: {result.get('app_key')}")
181
- if "dash_key" in result:
182
- lines.append(f"Dash Key: {result.get('dash_key')}")
183
- if "verified" in result:
184
- lines.append(f"Verified: {result.get('verified')}")
185
- data = result.get("data")
186
- if isinstance(data, dict):
187
- scalar_lines = _dict_scalar_lines(data)
188
- lines.extend(scalar_lines[:8])
183
+ def _format_tasks_list(result: dict[str, Any]) -> str:
184
+ data = result.get("data") if isinstance(result.get("data"), dict) else {}
185
+ items = data.get("items") if isinstance(data.get("items"), list) else []
186
+ rows = []
187
+ for item in items:
188
+ if not isinstance(item, dict):
189
+ continue
190
+ rows.append(
191
+ [
192
+ str(item.get("app_key") or ""),
193
+ str(item.get("record_id") or ""),
194
+ str(item.get("workflow_node_id") or ""),
195
+ str(item.get("title") or ""),
196
+ ]
197
+ )
198
+ output = _render_titled_table("Tasks", ["app_key", "record_id", "node_id", "title"], rows)
199
+ lines = output.rstrip("\n").split("\n")
189
200
  _append_warnings(lines, result.get("warnings"))
190
- _append_verification(lines, result.get("verification"))
191
- if not lines:
192
- return _format_generic(result)
193
201
  return "\n".join(lines) + "\n"
194
202
 
195
203
 
196
- def emit_json_result(result: dict[str, Any], *, stream: TextIO) -> None:
197
- json.dump(result, stream, ensure_ascii=False, indent=2)
198
- stream.write("\n")
204
+ def _format_builder_result(result: dict[str, Any]) -> str:
205
+ lines = [f"Code: {result.get('code')}", f"Message: {result.get('message')}"]
206
+ data = result.get("data")
207
+ if isinstance(data, dict):
208
+ lines.extend(_scalar_lines(data, limit=10))
209
+ _append_warnings(lines, result.get("warnings"))
210
+ return "\n".join(lines) + "\n"
199
211
 
200
212
 
201
213
  def _render_titled_table(title: str, headers: list[str], rows: list[list[str]]) -> str:
@@ -207,19 +219,20 @@ def _render_titled_table(title: str, headers: list[str], rows: list[list[str]])
207
219
  for row in rows:
208
220
  for index, cell in enumerate(row):
209
221
  widths[index] = max(widths[index], len(cell))
210
- header_line = " ".join(header.ljust(widths[index]) for index, header in enumerate(headers))
211
- lines.append(header_line)
222
+ lines.append(" ".join(header.ljust(widths[index]) for index, header in enumerate(headers)))
212
223
  lines.append(" ".join("-" * width for width in widths))
213
224
  for row in rows:
214
225
  lines.append(" ".join(cell.ljust(widths[index]) for index, cell in enumerate(row)))
215
226
  return "\n".join(lines) + "\n"
216
227
 
217
228
 
218
- def _dict_scalar_lines(payload: dict[str, Any]) -> list[str]:
229
+ def _scalar_lines(payload: dict[str, Any], *, limit: int) -> list[str]:
219
230
  lines: list[str] = []
220
231
  for key, value in payload.items():
221
232
  if isinstance(value, (str, int, float, bool)) or value is None:
222
233
  lines.append(f"{key}: {value}")
234
+ if len(lines) >= limit:
235
+ break
223
236
  return lines
224
237
 
225
238
 
@@ -231,39 +244,23 @@ def _append_warnings(lines: list[str], warnings: Any) -> None:
231
244
  if isinstance(warning, dict):
232
245
  code = warning.get("code")
233
246
  message = warning.get("message")
234
- if code or message:
235
- lines.append(f"- {code or 'WARNING'}: {message or ''}".rstrip())
236
- else:
237
- lines.append(f"- {json.dumps(warning, ensure_ascii=False)}")
247
+ lines.append(f"- {code or 'WARNING'}: {message or ''}".rstrip())
238
248
  else:
239
249
  lines.append(f"- {warning}")
240
250
 
241
251
 
242
- def _append_verification(lines: list[str], verification: Any) -> None:
243
- if not isinstance(verification, dict) or not verification:
244
- return
245
- lines.append("Verification:")
246
- for key, value in verification.items():
247
- if isinstance(value, (str, int, float, bool)) or value is None:
248
- lines.append(f"- {key}: {value}")
249
-
250
-
251
- def _first_present(payload: dict[str, Any], *keys: str) -> Any:
252
- for key in keys:
253
- if key in payload and payload.get(key) is not None:
254
- return payload.get(key)
255
- return None
256
-
257
-
258
252
  _FORMATTERS = {
259
- "auth_whoami": _format_whoami,
260
- "workspace_list": _format_workspace_list,
261
- "app_list": _format_app_items,
262
- "app_search": _format_app_items,
263
- "app_get": _format_app_get,
264
- "record_list": _format_record_list,
265
- "task_list": _format_task_list,
253
+ "me": _format_me,
254
+ "session": _format_me,
255
+ "ws_list": _format_workspace_list,
256
+ "ws_use": _format_workspace_use,
257
+ "apps_list": _format_apps_list,
258
+ "app_show": _format_app_show,
259
+ "records_list": _format_records_list,
260
+ "record_show": _format_record_show,
261
+ "record_write": _format_record_write,
266
262
  "import_verify": _format_import_verify,
267
263
  "import_status": _format_import_status,
268
- "builder_summary": _format_builder_summary,
264
+ "tasks_list": _format_tasks_list,
265
+ "builder_result": _format_builder_result,
269
266
  }