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.
Files changed (58) hide show
  1. hap_cli/README.md +194 -0
  2. hap_cli/README_CN.md +601 -0
  3. hap_cli/__init__.py +3 -0
  4. hap_cli/commands/__init__.py +1 -0
  5. hap_cli/commands/ai_cmd.py +224 -0
  6. hap_cli/commands/app_cmd.py +308 -0
  7. hap_cli/commands/calendar_cmd.py +138 -0
  8. hap_cli/commands/chat_cmd.py +101 -0
  9. hap_cli/commands/config_cmd.py +169 -0
  10. hap_cli/commands/contact_cmd.py +125 -0
  11. hap_cli/commands/department_cmd.py +168 -0
  12. hap_cli/commands/group_cmd.py +128 -0
  13. hap_cli/commands/instance_cmd.py +310 -0
  14. hap_cli/commands/node_cmd.py +538 -0
  15. hap_cli/commands/optionset_cmd.py +99 -0
  16. hap_cli/commands/page_cmd.py +102 -0
  17. hap_cli/commands/plugin_cmd.py +133 -0
  18. hap_cli/commands/post_cmd.py +155 -0
  19. hap_cli/commands/record_cmd.py +228 -0
  20. hap_cli/commands/role_cmd.py +221 -0
  21. hap_cli/commands/workflow_cmd.py +284 -0
  22. hap_cli/commands/worksheet_cmd.py +342 -0
  23. hap_cli/context.py +43 -0
  24. hap_cli/core/__init__.py +1 -0
  25. hap_cli/core/ai.py +133 -0
  26. hap_cli/core/app.py +307 -0
  27. hap_cli/core/auth.py +219 -0
  28. hap_cli/core/calendar_mod.py +114 -0
  29. hap_cli/core/chat.py +73 -0
  30. hap_cli/core/contact.py +85 -0
  31. hap_cli/core/department.py +131 -0
  32. hap_cli/core/flow_node.py +1001 -0
  33. hap_cli/core/group.py +99 -0
  34. hap_cli/core/instance.py +572 -0
  35. hap_cli/core/optionset.py +112 -0
  36. hap_cli/core/page.py +138 -0
  37. hap_cli/core/plugin.py +87 -0
  38. hap_cli/core/post.py +118 -0
  39. hap_cli/core/record.py +268 -0
  40. hap_cli/core/role.py +227 -0
  41. hap_cli/core/session.py +348 -0
  42. hap_cli/core/workflow.py +556 -0
  43. hap_cli/core/worksheet.py +403 -0
  44. hap_cli/hap_cli.py +105 -0
  45. hap_cli/skills/SKILL.md +383 -0
  46. hap_cli/skills/__init__.py +0 -0
  47. hap_cli/tests/__init__.py +1 -0
  48. hap_cli/tests/test_core.py +1824 -0
  49. hap_cli/tests/test_full_e2e.py +136 -0
  50. hap_cli/tests/test_integration.py +805 -0
  51. hap_cli/utils/__init__.py +1 -0
  52. hap_cli/utils/formatting.py +111 -0
  53. hap_cli/utils/options.py +10 -0
  54. hap_cli-0.5.0.dist-info/METADATA +223 -0
  55. hap_cli-0.5.0.dist-info/RECORD +58 -0
  56. hap_cli-0.5.0.dist-info/WHEEL +5 -0
  57. hap_cli-0.5.0.dist-info/entry_points.txt +2 -0
  58. 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)