hap-cli 0.5.0__py3-none-any.whl
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.
- hap_cli/README.md +194 -0
- hap_cli/README_CN.md +601 -0
- hap_cli/__init__.py +3 -0
- hap_cli/commands/__init__.py +1 -0
- hap_cli/commands/ai_cmd.py +224 -0
- hap_cli/commands/app_cmd.py +308 -0
- hap_cli/commands/calendar_cmd.py +138 -0
- hap_cli/commands/chat_cmd.py +101 -0
- hap_cli/commands/config_cmd.py +169 -0
- hap_cli/commands/contact_cmd.py +125 -0
- hap_cli/commands/department_cmd.py +168 -0
- hap_cli/commands/group_cmd.py +128 -0
- hap_cli/commands/instance_cmd.py +310 -0
- hap_cli/commands/node_cmd.py +538 -0
- hap_cli/commands/optionset_cmd.py +99 -0
- hap_cli/commands/page_cmd.py +102 -0
- hap_cli/commands/plugin_cmd.py +133 -0
- hap_cli/commands/post_cmd.py +155 -0
- hap_cli/commands/record_cmd.py +228 -0
- hap_cli/commands/role_cmd.py +221 -0
- hap_cli/commands/workflow_cmd.py +284 -0
- hap_cli/commands/worksheet_cmd.py +342 -0
- hap_cli/context.py +43 -0
- hap_cli/core/__init__.py +1 -0
- hap_cli/core/ai.py +133 -0
- hap_cli/core/app.py +307 -0
- hap_cli/core/auth.py +219 -0
- hap_cli/core/calendar_mod.py +114 -0
- hap_cli/core/chat.py +73 -0
- hap_cli/core/contact.py +85 -0
- hap_cli/core/department.py +131 -0
- hap_cli/core/flow_node.py +1001 -0
- hap_cli/core/group.py +99 -0
- hap_cli/core/instance.py +572 -0
- hap_cli/core/optionset.py +112 -0
- hap_cli/core/page.py +138 -0
- hap_cli/core/plugin.py +87 -0
- hap_cli/core/post.py +118 -0
- hap_cli/core/record.py +268 -0
- hap_cli/core/role.py +227 -0
- hap_cli/core/session.py +348 -0
- hap_cli/core/workflow.py +556 -0
- hap_cli/core/worksheet.py +403 -0
- hap_cli/hap_cli.py +105 -0
- hap_cli/skills/SKILL.md +383 -0
- hap_cli/skills/__init__.py +0 -0
- hap_cli/tests/__init__.py +1 -0
- hap_cli/tests/test_core.py +1824 -0
- hap_cli/tests/test_full_e2e.py +136 -0
- hap_cli/tests/test_integration.py +805 -0
- hap_cli/utils/__init__.py +1 -0
- hap_cli/utils/formatting.py +111 -0
- hap_cli/utils/options.py +10 -0
- hap_cli-0.5.0.dist-info/METADATA +223 -0
- hap_cli-0.5.0.dist-info/RECORD +58 -0
- hap_cli-0.5.0.dist-info/WHEEL +5 -0
- hap_cli-0.5.0.dist-info/entry_points.txt +2 -0
- hap_cli-0.5.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
"""CLI commands for workflow node management."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from hap_cli.context import pass_context
|
|
8
|
+
from hap_cli.core import flow_node as node_mod
|
|
9
|
+
from hap_cli.utils.formatting import output_json, output_table
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
NODE_TYPES_HELP = (
|
|
13
|
+
"Node type: 0=Start, 1=Branch, 2=BranchItem, 3=Fill, 4=Approval, "
|
|
14
|
+
"5=CC, 6=Action(data), 7=Search(single), 8=WebHook, 9=Formula, "
|
|
15
|
+
"10=Message, 11=Email, 12=Delay, 13=GetMoreRecord(multi/batch), "
|
|
16
|
+
"14=Code, 15=Link, 16=SubProcess, 17=Push, 18=File, 19=Template, "
|
|
17
|
+
"20=PBP, 21=JSONParse, 22=Auth, 25=API, 26=ApprovalProcess, "
|
|
18
|
+
"27=Notice, 28=Snapshot, 29=Loop, 31=AIGC, 32=Plugin, 33=Agent"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
ACTION_ID_HELP = (
|
|
22
|
+
"Action ID for data nodes: "
|
|
23
|
+
"1=AddRecord, 2=EditRecord, 3=DeleteRecord, 5=CreateRecord, "
|
|
24
|
+
"6=RefreshSingle, 20=Relation, "
|
|
25
|
+
"400=FromWorksheet, 401=FromRecord, 402=FromAdd, "
|
|
26
|
+
"406=WorksheetFind, 407=BatchFind, "
|
|
27
|
+
"412=BatchUpdate, 413=BatchDelete, 415=RefreshMultiple, "
|
|
28
|
+
"421=RecordUpdate, 422=RecordDelete"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@click.group()
|
|
33
|
+
def node():
|
|
34
|
+
"""Workflow node management."""
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@node.command("list")
|
|
39
|
+
@click.argument("process_id")
|
|
40
|
+
@pass_context
|
|
41
|
+
def node_list(ctx, process_id):
|
|
42
|
+
"""List all nodes in a workflow."""
|
|
43
|
+
try:
|
|
44
|
+
session = ctx.get_session()
|
|
45
|
+
data = node_mod.get_nodes(session, process_id)
|
|
46
|
+
ctx.output(data, lambda d: output_json(d))
|
|
47
|
+
except Exception as e:
|
|
48
|
+
ctx.handle_error(e)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@node.command("get")
|
|
52
|
+
@click.argument("process_id")
|
|
53
|
+
@click.argument("node_id")
|
|
54
|
+
@click.option("--type", "node_type", default=0, type=int, help=NODE_TYPES_HELP)
|
|
55
|
+
@pass_context
|
|
56
|
+
def node_get(ctx, process_id, node_id, node_type):
|
|
57
|
+
"""Get node detail/configuration."""
|
|
58
|
+
try:
|
|
59
|
+
session = ctx.get_session()
|
|
60
|
+
data = node_mod.get_node_detail(session, process_id, node_id, node_type)
|
|
61
|
+
ctx.output(data, lambda d: output_json(d))
|
|
62
|
+
except Exception as e:
|
|
63
|
+
ctx.handle_error(e)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@node.command("add")
|
|
67
|
+
@click.argument("process_id")
|
|
68
|
+
@click.option("--type", "node_type", required=True, type=int, help=NODE_TYPES_HELP)
|
|
69
|
+
@click.option("--name", "-n", default="", help="Node name")
|
|
70
|
+
@click.option("--after", "select_node_id", default="", help="Insert after this node ID")
|
|
71
|
+
@click.option("--action-id", "-a", default="", help=ACTION_ID_HELP)
|
|
72
|
+
@pass_context
|
|
73
|
+
def node_add(ctx, process_id, node_type, name, select_node_id, action_id):
|
|
74
|
+
"""Add a node to a workflow."""
|
|
75
|
+
try:
|
|
76
|
+
session = ctx.get_session()
|
|
77
|
+
data = node_mod.add_node(
|
|
78
|
+
session, process_id, node_type,
|
|
79
|
+
name=name, select_node_id=select_node_id,
|
|
80
|
+
action_id=action_id,
|
|
81
|
+
)
|
|
82
|
+
ctx.output(data, lambda d: click.echo(f"Node added: {d}"))
|
|
83
|
+
except Exception as e:
|
|
84
|
+
ctx.handle_error(e)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@node.command("delete")
|
|
88
|
+
@click.argument("process_id")
|
|
89
|
+
@click.argument("node_id")
|
|
90
|
+
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation")
|
|
91
|
+
@pass_context
|
|
92
|
+
def node_delete(ctx, process_id, node_id, yes):
|
|
93
|
+
"""Delete a node from a workflow."""
|
|
94
|
+
try:
|
|
95
|
+
if not yes:
|
|
96
|
+
click.confirm(f"Delete node {node_id}?", abort=True)
|
|
97
|
+
session = ctx.get_session()
|
|
98
|
+
data = node_mod.delete_node(session, process_id, node_id)
|
|
99
|
+
ctx.output(data, lambda d: click.echo("Node deleted."))
|
|
100
|
+
except Exception as e:
|
|
101
|
+
ctx.handle_error(e)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@node.command("rename")
|
|
105
|
+
@click.argument("process_id")
|
|
106
|
+
@click.argument("node_id")
|
|
107
|
+
@click.option("--name", "-n", required=True, help="New node name")
|
|
108
|
+
@pass_context
|
|
109
|
+
def node_rename(ctx, process_id, node_id, name):
|
|
110
|
+
"""Rename a node."""
|
|
111
|
+
try:
|
|
112
|
+
session = ctx.get_session()
|
|
113
|
+
data = node_mod.update_node_name(session, process_id, node_id, name)
|
|
114
|
+
ctx.output(data, lambda d: click.echo("Node renamed."))
|
|
115
|
+
except Exception as e:
|
|
116
|
+
ctx.handle_error(e)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@node.command("desc")
|
|
120
|
+
@click.argument("process_id")
|
|
121
|
+
@click.argument("node_id")
|
|
122
|
+
@click.option("--desc", "-d", default="", help="Node description")
|
|
123
|
+
@click.option("--alias", "-a", default="", help="Node alias")
|
|
124
|
+
@pass_context
|
|
125
|
+
def node_desc(ctx, process_id, node_id, desc, alias):
|
|
126
|
+
"""Update node description/alias."""
|
|
127
|
+
try:
|
|
128
|
+
session = ctx.get_session()
|
|
129
|
+
data = node_mod.update_node_desc(session, process_id, node_id, desc, alias)
|
|
130
|
+
ctx.output(data, lambda d: click.echo("Node description updated."))
|
|
131
|
+
except Exception as e:
|
|
132
|
+
ctx.handle_error(e)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@node.command("save")
|
|
136
|
+
@click.argument("process_id")
|
|
137
|
+
@click.argument("node_id")
|
|
138
|
+
@click.option("--type", "node_type", required=True, type=int, help=NODE_TYPES_HELP)
|
|
139
|
+
@click.option("--config", "-c", required=True, help="JSON config string for the node")
|
|
140
|
+
@click.option("--name", "-n", default="", help="Node name")
|
|
141
|
+
@pass_context
|
|
142
|
+
def node_save(ctx, process_id, node_id, node_type, config, name):
|
|
143
|
+
"""Save full node configuration (JSON string).
|
|
144
|
+
|
|
145
|
+
The config JSON varies by node type. Use 'node get' to see current config.
|
|
146
|
+
"""
|
|
147
|
+
try:
|
|
148
|
+
session = ctx.get_session()
|
|
149
|
+
config_dict = json.loads(config)
|
|
150
|
+
data = node_mod.save_node(
|
|
151
|
+
session, process_id, node_id, node_type, config_dict, name=name,
|
|
152
|
+
)
|
|
153
|
+
ctx.output(data, lambda d: click.echo("Node config saved."))
|
|
154
|
+
except json.JSONDecodeError as e:
|
|
155
|
+
ctx.handle_error(click.UsageError(f"Invalid JSON: {e}"))
|
|
156
|
+
except Exception as e:
|
|
157
|
+
ctx.handle_error(e)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@node.command("test-code")
|
|
161
|
+
@click.argument("process_id")
|
|
162
|
+
@click.argument("node_id")
|
|
163
|
+
@click.option("--code", "-c", required=True, help="Code to test")
|
|
164
|
+
@click.option("--input", "-i", "input_json", default="", help="Input data JSON: [{name,value,type}]")
|
|
165
|
+
@pass_context
|
|
166
|
+
def node_test_code(ctx, process_id, node_id, code, input_json):
|
|
167
|
+
"""Test a code block node."""
|
|
168
|
+
try:
|
|
169
|
+
session = ctx.get_session()
|
|
170
|
+
input_data = json.loads(input_json) if input_json else None
|
|
171
|
+
data = node_mod.test_code(session, process_id, node_id, code, input_data)
|
|
172
|
+
ctx.output(data, lambda d: output_json(d))
|
|
173
|
+
except json.JSONDecodeError as e:
|
|
174
|
+
ctx.handle_error(click.UsageError(f"Invalid JSON: {e}"))
|
|
175
|
+
except Exception as e:
|
|
176
|
+
ctx.handle_error(e)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@node.command("test-webhook")
|
|
180
|
+
@click.argument("process_id")
|
|
181
|
+
@click.argument("node_id")
|
|
182
|
+
@click.option("--url", "-u", required=True, help="Target URL")
|
|
183
|
+
@click.option("--method", "-m", default="POST", help="HTTP method")
|
|
184
|
+
@click.option("--body", "-b", default="", help="Request body (JSON string)")
|
|
185
|
+
@click.option("--headers", "-H", default="", help="Headers JSON: [{name,value}]")
|
|
186
|
+
@click.option("--content-type", default="application/json", help="Content-Type")
|
|
187
|
+
@pass_context
|
|
188
|
+
def node_test_webhook(ctx, process_id, node_id, url, method, body, headers, content_type):
|
|
189
|
+
"""Test a WebHook node."""
|
|
190
|
+
try:
|
|
191
|
+
session = ctx.get_session()
|
|
192
|
+
headers_list = json.loads(headers) if headers else None
|
|
193
|
+
data = node_mod.test_webhook(
|
|
194
|
+
session, process_id, node_id, url,
|
|
195
|
+
method=method, body=body, headers=headers_list,
|
|
196
|
+
content_type=content_type,
|
|
197
|
+
)
|
|
198
|
+
ctx.output(data, lambda d: output_json(d))
|
|
199
|
+
except json.JSONDecodeError as e:
|
|
200
|
+
ctx.handle_error(click.UsageError(f"Invalid JSON: {e}"))
|
|
201
|
+
except Exception as e:
|
|
202
|
+
ctx.handle_error(e)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@node.command("test-ai")
|
|
206
|
+
@click.argument("process_id")
|
|
207
|
+
@click.argument("node_id")
|
|
208
|
+
@click.option("--prompt", "-p", required=True, help="User prompt")
|
|
209
|
+
@click.option("--model", "-m", default="", help="AI model name")
|
|
210
|
+
@click.option("--system", "-s", default="", help="System message")
|
|
211
|
+
@click.option("--temperature", "-t", default=0.7, type=float, help="Temperature 0-1")
|
|
212
|
+
@pass_context
|
|
213
|
+
def node_test_ai(ctx, process_id, node_id, prompt, model, system, temperature):
|
|
214
|
+
"""Test an AI text generation node."""
|
|
215
|
+
try:
|
|
216
|
+
session = ctx.get_session()
|
|
217
|
+
data = node_mod.test_aigc(
|
|
218
|
+
session, process_id, node_id, prompt,
|
|
219
|
+
model=model, system_message=system, temperature=temperature,
|
|
220
|
+
)
|
|
221
|
+
ctx.output(data, lambda d: output_json(d))
|
|
222
|
+
except Exception as e:
|
|
223
|
+
ctx.handle_error(e)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@node.command("form-property")
|
|
227
|
+
@click.argument("process_id")
|
|
228
|
+
@click.argument("node_id")
|
|
229
|
+
@pass_context
|
|
230
|
+
def node_form_property(ctx, process_id, node_id):
|
|
231
|
+
"""Get node form properties."""
|
|
232
|
+
try:
|
|
233
|
+
session = ctx.get_session()
|
|
234
|
+
data = node_mod.get_node_form_property(session, process_id, node_id)
|
|
235
|
+
ctx.output(data, lambda d: output_json(d))
|
|
236
|
+
except Exception as e:
|
|
237
|
+
ctx.handle_error(e)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@node.command("sub-processes")
|
|
241
|
+
@click.argument("process_id")
|
|
242
|
+
@pass_context
|
|
243
|
+
def node_sub_processes(ctx, process_id):
|
|
244
|
+
"""List available sub-processes."""
|
|
245
|
+
try:
|
|
246
|
+
session = ctx.get_session()
|
|
247
|
+
data = node_mod.get_sub_process_list(session, process_id)
|
|
248
|
+
ctx.output(data, lambda d: output_json(d))
|
|
249
|
+
except Exception as e:
|
|
250
|
+
ctx.handle_error(e)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@node.command("json-to-controls")
|
|
254
|
+
@click.option("--json-str", "-j", required=True, help="JSON string to convert")
|
|
255
|
+
@pass_context
|
|
256
|
+
def node_json_to_controls(ctx, json_str):
|
|
257
|
+
"""Convert JSON to workflow controls."""
|
|
258
|
+
try:
|
|
259
|
+
session = ctx.get_session()
|
|
260
|
+
data = node_mod.json_to_controls(session, json_str)
|
|
261
|
+
ctx.output(data, lambda d: output_json(d))
|
|
262
|
+
except Exception as e:
|
|
263
|
+
ctx.handle_error(e)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@node.command("code-templates")
|
|
267
|
+
@click.option("--keyword", "-k", default="", help="Search keyword")
|
|
268
|
+
@click.option("--page-size", "-n", default=50, help="Items per page")
|
|
269
|
+
@click.option("--page", "-p", default=1, help="Page number")
|
|
270
|
+
@pass_context
|
|
271
|
+
def node_code_templates(ctx, keyword, page_size, page):
|
|
272
|
+
"""List code templates."""
|
|
273
|
+
try:
|
|
274
|
+
session = ctx.get_session()
|
|
275
|
+
data = node_mod.get_code_template_list(
|
|
276
|
+
session, keyword=keyword, page_index=page, page_size=page_size,
|
|
277
|
+
)
|
|
278
|
+
ctx.output(data, lambda d: output_json(d))
|
|
279
|
+
except Exception as e:
|
|
280
|
+
ctx.handle_error(e)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
@node.command("create-code-template")
|
|
284
|
+
@click.option("--name", "-n", required=True, help="Template name")
|
|
285
|
+
@click.option("--code", "-c", required=True, help="Template code")
|
|
286
|
+
@pass_context
|
|
287
|
+
def node_create_code_template(ctx, name, code):
|
|
288
|
+
"""Create a code template."""
|
|
289
|
+
try:
|
|
290
|
+
session = ctx.get_session()
|
|
291
|
+
data = node_mod.create_code_template(session, name, code)
|
|
292
|
+
ctx.output(data, lambda d: click.echo(f"Template created: {d}"))
|
|
293
|
+
except Exception as e:
|
|
294
|
+
ctx.handle_error(e)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
# ── Data Processing Node Commands ────────────────────────────────────────
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@node.command("save-action")
|
|
301
|
+
@click.argument("process_id")
|
|
302
|
+
@click.argument("node_id")
|
|
303
|
+
@click.option("--action-id", "-a", required=True, help=(
|
|
304
|
+
"Action: 1=AddRecord, 2=EditRecord, 3=DeleteRecord, "
|
|
305
|
+
"5=CreateRecord, 6=RefreshSingle, 20=Relation"))
|
|
306
|
+
@click.option("--app-id", required=True, help="Target worksheet ID")
|
|
307
|
+
@click.option("--fields", "-f", default="", help="Field mapping JSON: [{fieldId,type,fieldValue}]")
|
|
308
|
+
@click.option("--name", "-n", default="", help="Node name")
|
|
309
|
+
@click.option("--select-node-id", "-s", default="", help="Source data node ID")
|
|
310
|
+
@click.option("--condition", default="", help="Filter conditions JSON")
|
|
311
|
+
@click.option("--sorts", default="", help="Sort rules JSON")
|
|
312
|
+
@pass_context
|
|
313
|
+
def node_save_action(ctx, process_id, node_id, action_id, app_id, fields,
|
|
314
|
+
name, select_node_id, condition, sorts):
|
|
315
|
+
"""Configure a data ACTION node (add/edit/delete record).
|
|
316
|
+
|
|
317
|
+
\b
|
|
318
|
+
Examples:
|
|
319
|
+
# Add record node
|
|
320
|
+
hap-cli node save-action PID NID -a 1 --app-id WSID \\
|
|
321
|
+
-f '[{"fieldId":"c001","type":2,"fieldValue":"hello"}]'
|
|
322
|
+
# Edit record node
|
|
323
|
+
hap-cli node save-action PID NID -a 2 --app-id WSID -s SOURCE_NID \\
|
|
324
|
+
-f '[{"fieldId":"c001","type":2,"fieldValue":"updated"}]'
|
|
325
|
+
# Delete record node
|
|
326
|
+
hap-cli node save-action PID NID -a 3 --app-id WSID -s SOURCE_NID
|
|
327
|
+
"""
|
|
328
|
+
try:
|
|
329
|
+
session = ctx.get_session()
|
|
330
|
+
fields_list = json.loads(fields) if fields else None
|
|
331
|
+
cond_list = json.loads(condition) if condition else None
|
|
332
|
+
sorts_list = json.loads(sorts) if sorts else None
|
|
333
|
+
data = node_mod.save_action_node(
|
|
334
|
+
session, process_id, node_id, action_id, app_id,
|
|
335
|
+
fields=fields_list, name=name, select_node_id=select_node_id,
|
|
336
|
+
operate_condition=cond_list, sorts=sorts_list,
|
|
337
|
+
)
|
|
338
|
+
ctx.output(data, lambda d: click.echo("Action node saved."))
|
|
339
|
+
except json.JSONDecodeError as e:
|
|
340
|
+
ctx.handle_error(click.UsageError(f"Invalid JSON: {e}"))
|
|
341
|
+
except Exception as e:
|
|
342
|
+
ctx.handle_error(e)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
@node.command("save-search")
|
|
346
|
+
@click.argument("process_id")
|
|
347
|
+
@click.argument("node_id")
|
|
348
|
+
@click.option("--action-id", "-a", required=True, help=(
|
|
349
|
+
"Search type: 406=WorksheetFind, 407=BatchFind, "
|
|
350
|
+
"420=RecordLinkFind, 421=RecordUpdate, 422=RecordDelete"))
|
|
351
|
+
@click.option("--app-id", default="", help="Target worksheet ID (for 406)")
|
|
352
|
+
@click.option("--fields", "-f", default="", help="Return fields JSON")
|
|
353
|
+
@click.option("--name", "-n", default="", help="Node name")
|
|
354
|
+
@click.option("--select-node-id", "-s", default="", help="Source node ID (for 407)")
|
|
355
|
+
@click.option("--condition", default="", help="Query conditions JSON")
|
|
356
|
+
@click.option("--sorts", default="", help="Sort rules JSON")
|
|
357
|
+
@click.option("--random", is_flag=True, help="Pick random record")
|
|
358
|
+
@click.option("--not-found", "execute_type", default=0, type=int,
|
|
359
|
+
help="When not found: 0=abort, 1=create new, 2=continue")
|
|
360
|
+
@pass_context
|
|
361
|
+
def node_save_search(ctx, process_id, node_id, action_id, app_id, fields,
|
|
362
|
+
name, select_node_id, condition, sorts, random, execute_type):
|
|
363
|
+
"""Configure a SEARCH node (get single record).
|
|
364
|
+
|
|
365
|
+
\b
|
|
366
|
+
Examples:
|
|
367
|
+
# Find a record from worksheet by condition
|
|
368
|
+
hap-cli node save-search PID NID -a 406 --app-id WSID \\
|
|
369
|
+
--condition '[{"fieldId":"c001","conditionId":"1","value":["test"]}]'
|
|
370
|
+
# Find from multi-record node
|
|
371
|
+
hap-cli node save-search PID NID -a 407 -s MULTI_NODE_ID
|
|
372
|
+
# Find and update
|
|
373
|
+
hap-cli node save-search PID NID -a 421 --app-id WSID \\
|
|
374
|
+
-f '[{"fieldId":"c002","type":2,"fieldValue":"new val"}]'
|
|
375
|
+
# Find and delete
|
|
376
|
+
hap-cli node save-search PID NID -a 422 --app-id WSID
|
|
377
|
+
"""
|
|
378
|
+
try:
|
|
379
|
+
session = ctx.get_session()
|
|
380
|
+
fields_list = json.loads(fields) if fields else None
|
|
381
|
+
cond_list = json.loads(condition) if condition else None
|
|
382
|
+
sorts_list = json.loads(sorts) if sorts else None
|
|
383
|
+
data = node_mod.save_search_node(
|
|
384
|
+
session, process_id, node_id, action_id,
|
|
385
|
+
app_id=app_id, fields=fields_list, name=name,
|
|
386
|
+
select_node_id=select_node_id, operate_condition=cond_list,
|
|
387
|
+
sorts=sorts_list, random=random, execute_type=execute_type,
|
|
388
|
+
)
|
|
389
|
+
ctx.output(data, lambda d: click.echo("Search node saved."))
|
|
390
|
+
except json.JSONDecodeError as e:
|
|
391
|
+
ctx.handle_error(click.UsageError(f"Invalid JSON: {e}"))
|
|
392
|
+
except Exception as e:
|
|
393
|
+
ctx.handle_error(e)
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
@node.command("save-get-more")
|
|
397
|
+
@click.argument("process_id")
|
|
398
|
+
@click.argument("node_id")
|
|
399
|
+
@click.option("--action-id", "-a", required=True, help=(
|
|
400
|
+
"Type: 400=FromWorksheet, 401=FromRecord, 402=FromAdd, "
|
|
401
|
+
"403=FromArray, 412=BatchUpdate, 413=BatchDelete, 415=RefreshMultiple"))
|
|
402
|
+
@click.option("--app-id", default="", help="Target worksheet ID (for 400/412/413)")
|
|
403
|
+
@click.option("--fields", "-f", default="", help="Field mapping JSON")
|
|
404
|
+
@click.option("--name", "-n", default="", help="Node name")
|
|
405
|
+
@click.option("--select-node-id", "-s", default="", help="Source node ID (for 401)")
|
|
406
|
+
@click.option("--condition", default="", help="Filter conditions JSON")
|
|
407
|
+
@click.option("--sorts", default="", help="Sort rules JSON")
|
|
408
|
+
@click.option("--limit", "limit_json", default="", help="Record limit JSON: {fieldValue, fieldNodeId}")
|
|
409
|
+
@click.option("--random", is_flag=True, help="Pick random records")
|
|
410
|
+
@pass_context
|
|
411
|
+
def node_save_get_more(ctx, process_id, node_id, action_id, app_id, fields,
|
|
412
|
+
name, select_node_id, condition, sorts, limit_json, random):
|
|
413
|
+
"""Configure a GET_MORE_RECORD node (multi-record/batch ops).
|
|
414
|
+
|
|
415
|
+
\b
|
|
416
|
+
Examples:
|
|
417
|
+
# Get multiple records from worksheet
|
|
418
|
+
hap-cli node save-get-more PID NID -a 400 --app-id WSID \\
|
|
419
|
+
--condition '[{"fieldId":"c001","conditionId":"12","value":["100"]}]'
|
|
420
|
+
# Get related records from a record
|
|
421
|
+
hap-cli node save-get-more PID NID -a 401 -s SOURCE_NID
|
|
422
|
+
# Batch update records
|
|
423
|
+
hap-cli node save-get-more PID NID -a 412 --app-id WSID \\
|
|
424
|
+
-f '[{"fieldId":"c001","type":2,"fieldValue":"batch val"}]'
|
|
425
|
+
# Batch delete records
|
|
426
|
+
hap-cli node save-get-more PID NID -a 413 --app-id WSID
|
|
427
|
+
"""
|
|
428
|
+
try:
|
|
429
|
+
session = ctx.get_session()
|
|
430
|
+
fields_list = json.loads(fields) if fields else None
|
|
431
|
+
cond_list = json.loads(condition) if condition else None
|
|
432
|
+
sorts_list = json.loads(sorts) if sorts else None
|
|
433
|
+
limit_dict = json.loads(limit_json) if limit_json else None
|
|
434
|
+
data = node_mod.save_get_more_record_node(
|
|
435
|
+
session, process_id, node_id, action_id,
|
|
436
|
+
app_id=app_id, fields=fields_list, name=name,
|
|
437
|
+
select_node_id=select_node_id, operate_condition=cond_list,
|
|
438
|
+
sorts=sorts_list, number_field_value=limit_dict, random=random,
|
|
439
|
+
)
|
|
440
|
+
ctx.output(data, lambda d: click.echo("GetMoreRecord node saved."))
|
|
441
|
+
except json.JSONDecodeError as e:
|
|
442
|
+
ctx.handle_error(click.UsageError(f"Invalid JSON: {e}"))
|
|
443
|
+
except Exception as e:
|
|
444
|
+
ctx.handle_error(e)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
@node.command("controls")
|
|
448
|
+
@click.argument("process_id")
|
|
449
|
+
@click.argument("node_id")
|
|
450
|
+
@click.option("--app-id", required=True, help="Worksheet ID to get controls for")
|
|
451
|
+
@click.option("--action-id", "-a", default="", help="Action context")
|
|
452
|
+
@pass_context
|
|
453
|
+
def node_controls(ctx, process_id, node_id, app_id, action_id):
|
|
454
|
+
"""Get worksheet fields/controls available for node configuration.
|
|
455
|
+
|
|
456
|
+
Use this to discover field IDs before configuring data processing nodes.
|
|
457
|
+
"""
|
|
458
|
+
try:
|
|
459
|
+
session = ctx.get_session()
|
|
460
|
+
data = node_mod.get_app_template_controls(
|
|
461
|
+
session, process_id, node_id, app_id, action_id=action_id,
|
|
462
|
+
)
|
|
463
|
+
ctx.output(data, lambda d: output_json(d))
|
|
464
|
+
except Exception as e:
|
|
465
|
+
ctx.handle_error(e)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
@node.command("types")
|
|
469
|
+
@pass_context
|
|
470
|
+
def node_types(ctx):
|
|
471
|
+
"""Show all available node types and action IDs."""
|
|
472
|
+
types_table = [
|
|
473
|
+
["Type", "ID", "Description"],
|
|
474
|
+
["START", "0", "Start event"],
|
|
475
|
+
["BRANCH", "1", "Conditional branch"],
|
|
476
|
+
["BRANCH_ITEM", "2", "Branch item"],
|
|
477
|
+
["FILL", "3", "Fill-in form"],
|
|
478
|
+
["APPROVAL", "4", "Approval task"],
|
|
479
|
+
["CC", "5", "CC notification"],
|
|
480
|
+
["ACTION", "6", "Data processing (add/edit/delete record)"],
|
|
481
|
+
["SEARCH", "7", "Get single record"],
|
|
482
|
+
["WEBHOOK", "8", "HTTP request"],
|
|
483
|
+
["FORMULA", "9", "Formula/calculation"],
|
|
484
|
+
["MESSAGE", "10", "SMS notification"],
|
|
485
|
+
["EMAIL", "11", "Email"],
|
|
486
|
+
["DELAY", "12", "Delay/timer"],
|
|
487
|
+
["GET_MORE_RECORD", "13", "Get multiple records / batch ops"],
|
|
488
|
+
["CODE", "14", "Code block"],
|
|
489
|
+
["LINK", "15", "Get link"],
|
|
490
|
+
["SUB_PROCESS", "16", "Sub-process"],
|
|
491
|
+
["PUSH", "17", "UI push notification"],
|
|
492
|
+
["FILE", "18", "File/print"],
|
|
493
|
+
["TEMPLATE", "19", "Template message"],
|
|
494
|
+
["PBP", "20", "Packaged Business Process"],
|
|
495
|
+
["JSON_PARSE", "21", "JSON parser"],
|
|
496
|
+
["AUTHENTICATION", "22", "Authentication"],
|
|
497
|
+
["API_PACKAGE", "24", "API package"],
|
|
498
|
+
["API", "25", "Integrated API call"],
|
|
499
|
+
["APPROVAL_PROCESS", "26", "Initiate approval"],
|
|
500
|
+
["NOTICE", "27", "Notice"],
|
|
501
|
+
["SNAPSHOT", "28", "Snapshot"],
|
|
502
|
+
["LOOP", "29", "Loop"],
|
|
503
|
+
["RETURN", "30", "Return"],
|
|
504
|
+
["AIGC", "31", "AI text generation"],
|
|
505
|
+
["PLUGIN", "32", "Plugin"],
|
|
506
|
+
["AGENT", "33", "AI Agent"],
|
|
507
|
+
]
|
|
508
|
+
if ctx.json_mode:
|
|
509
|
+
from hap_cli.utils.formatting import output_json as _oj
|
|
510
|
+
_oj({t[0]: int(t[1]) for t in types_table[1:]})
|
|
511
|
+
else:
|
|
512
|
+
cols = types_table[0]
|
|
513
|
+
click.echo("\n=== Node Types ===")
|
|
514
|
+
output_table([dict(zip(cols, r)) for r in types_table[1:]], cols)
|
|
515
|
+
|
|
516
|
+
actions_table = [
|
|
517
|
+
["Action", "ID", "Node Type", "Description"],
|
|
518
|
+
["ADD_RECORD", "1", "6(ACTION)", "Add single record"],
|
|
519
|
+
["EDIT_RECORD", "2", "6(ACTION)", "Edit single record"],
|
|
520
|
+
["DELETE_RECORD", "3", "6(ACTION)", "Delete single record"],
|
|
521
|
+
["CREATE_RECORD", "5", "6(ACTION)", "Create record"],
|
|
522
|
+
["REFRESH_SINGLE", "6", "6(ACTION)", "Refresh single record data"],
|
|
523
|
+
["RELATION", "20", "6(ACTION)", "Get related record"],
|
|
524
|
+
["WORKSHEET_FIND", "406", "7(SEARCH)", "Find one from worksheet"],
|
|
525
|
+
["BATCH_FIND", "407", "7(SEARCH)", "Find one from multi-records"],
|
|
526
|
+
["RECORD_LINK_FIND", "420", "7(SEARCH)", "Find via record link"],
|
|
527
|
+
["RECORD_UPDATE", "421", "7(SEARCH)", "Find and update"],
|
|
528
|
+
["RECORD_DELETE", "422", "7(SEARCH)", "Find and delete"],
|
|
529
|
+
["FROM_WORKSHEET", "400", "13(GET_MORE)", "Get multi from worksheet"],
|
|
530
|
+
["FROM_RECORD", "401", "13(GET_MORE)", "Get multi from record relation"],
|
|
531
|
+
["FROM_ADD", "402", "13(GET_MORE)", "Get multi from add node"],
|
|
532
|
+
["BATCH_UPDATE", "412", "13(GET_MORE)", "Batch update records"],
|
|
533
|
+
["BATCH_DELETE", "413", "13(GET_MORE)", "Batch delete records"],
|
|
534
|
+
["REFRESH_MULTIPLE", "415", "13(GET_MORE)", "Refresh multiple records"],
|
|
535
|
+
]
|
|
536
|
+
acols = actions_table[0]
|
|
537
|
+
click.echo("\n=== Action IDs (for data processing) ===")
|
|
538
|
+
output_table([dict(zip(acols, r)) for r in actions_table[1:]], acols)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""CLI commands for option set management."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from hap_cli.context import pass_context
|
|
8
|
+
from hap_cli.core import optionset as optionset_mod
|
|
9
|
+
from hap_cli.utils.formatting import output_json, output_table
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.group()
|
|
13
|
+
def optionset():
|
|
14
|
+
"""Option set management."""
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@optionset.command("list")
|
|
19
|
+
@click.argument("app_id")
|
|
20
|
+
@pass_context
|
|
21
|
+
def optionset_list(ctx, app_id):
|
|
22
|
+
"""List option set collections for an application."""
|
|
23
|
+
try:
|
|
24
|
+
session = ctx.get_session()
|
|
25
|
+
data = optionset_mod.get_collections(session, app_id)
|
|
26
|
+
ctx.output(
|
|
27
|
+
data,
|
|
28
|
+
lambda d: output_table(
|
|
29
|
+
d if isinstance(d, list) else [],
|
|
30
|
+
["collectId", "name"],
|
|
31
|
+
["Collection ID", "Name"],
|
|
32
|
+
),
|
|
33
|
+
)
|
|
34
|
+
except Exception as e:
|
|
35
|
+
ctx.handle_error(e)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@optionset.command("get")
|
|
39
|
+
@click.argument("collect_id")
|
|
40
|
+
@pass_context
|
|
41
|
+
def optionset_get(ctx, collect_id):
|
|
42
|
+
"""Get option set collection detail."""
|
|
43
|
+
try:
|
|
44
|
+
session = ctx.get_session()
|
|
45
|
+
data = optionset_mod.get_collection(session, collect_id)
|
|
46
|
+
ctx.output(data, lambda d: output_json(d))
|
|
47
|
+
except Exception as e:
|
|
48
|
+
ctx.handle_error(e)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@optionset.command("save")
|
|
52
|
+
@click.option("--app-id", required=True, help="Application ID")
|
|
53
|
+
@click.option("--name", "-n", required=True, help="Collection name")
|
|
54
|
+
@click.option("--options", "-o", required=True, help="Options as JSON string")
|
|
55
|
+
@click.option("--collect-id", default="", help="Collection ID (for update)")
|
|
56
|
+
@pass_context
|
|
57
|
+
def optionset_save(ctx, app_id, name, options, collect_id):
|
|
58
|
+
"""Create or update an option set collection."""
|
|
59
|
+
try:
|
|
60
|
+
session = ctx.get_session()
|
|
61
|
+
opts = json.loads(options)
|
|
62
|
+
data = optionset_mod.save_collection(
|
|
63
|
+
session, app_id, name, opts, collect_id,
|
|
64
|
+
)
|
|
65
|
+
ctx.output(data, lambda d: click.echo(f"Collection saved: {d}"))
|
|
66
|
+
except json.JSONDecodeError:
|
|
67
|
+
ctx.handle_error(ValueError("Invalid JSON for --options"))
|
|
68
|
+
except Exception as e:
|
|
69
|
+
ctx.handle_error(e)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@optionset.command("delete")
|
|
73
|
+
@click.argument("collect_id")
|
|
74
|
+
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation")
|
|
75
|
+
@pass_context
|
|
76
|
+
def optionset_delete(ctx, collect_id, yes):
|
|
77
|
+
"""Delete an option set collection."""
|
|
78
|
+
try:
|
|
79
|
+
if not yes:
|
|
80
|
+
click.confirm(f"Delete collection {collect_id}?", abort=True)
|
|
81
|
+
session = ctx.get_session()
|
|
82
|
+
data = optionset_mod.delete_collection(session, collect_id)
|
|
83
|
+
ctx.output(data, lambda d: click.echo("Collection deleted."))
|
|
84
|
+
except Exception as e:
|
|
85
|
+
ctx.handle_error(e)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@optionset.command("move")
|
|
89
|
+
@click.argument("collect_id")
|
|
90
|
+
@click.argument("new_app_id")
|
|
91
|
+
@pass_context
|
|
92
|
+
def optionset_move(ctx, collect_id, new_app_id):
|
|
93
|
+
"""Move an option set collection to another application."""
|
|
94
|
+
try:
|
|
95
|
+
session = ctx.get_session()
|
|
96
|
+
data = optionset_mod.move_collection(session, collect_id, new_app_id)
|
|
97
|
+
ctx.output(data, lambda d: click.echo("Collection moved."))
|
|
98
|
+
except Exception as e:
|
|
99
|
+
ctx.handle_error(e)
|