@shulex/cxclawctl 0.1.2 → 0.1.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.
Files changed (208) hide show
  1. package/README.md +25 -22
  2. package/dist/cli.js +2202 -127
  3. package/dist/cli.js.map +1 -1
  4. package/package.json +19 -6
  5. package/capabilities.lock.json +0 -898
  6. package/requirements-cli.txt +0 -2
  7. package/skills/__lib__/__init__.py +0 -0
  8. package/skills/__lib__/skill-runtime/bootstrap.py +0 -134
  9. package/skills/__lib__/solvea-api/__init__.py +0 -0
  10. package/skills/__lib__/solvea-api/client.py +0 -1944
  11. package/skills/__lib__/solvea-api/dashboard.py +0 -100
  12. package/skills/__lib__/solvea-api/solvea_api.py +0 -1341
  13. package/skills/agent-analytics/SKILL.md +0 -74
  14. package/skills/agent-analytics/references/chat-report-fields.md +0 -36
  15. package/skills/agent-analytics/references/report-fields.md +0 -130
  16. package/skills/agent-analytics/references/transcript.md +0 -110
  17. package/skills/agent-builder/SKILL.md +0 -42
  18. package/skills/agent-builder/interfaces/fallback-chatflow.md +0 -47
  19. package/skills/agent-builder/references/chatflow/graph-spec.md +0 -259
  20. package/skills/agent-builder/references/chatflow/node-selection.md +0 -89
  21. package/skills/agent-builder/references/chatflow/nodes/agent.md +0 -350
  22. package/skills/agent-builder/references/chatflow/nodes/business-track.md +0 -43
  23. package/skills/agent-builder/references/chatflow/nodes/code.md +0 -157
  24. package/skills/agent-builder/references/chatflow/nodes/collect-order.md +0 -47
  25. package/skills/agent-builder/references/chatflow/nodes/finish.md +0 -34
  26. package/skills/agent-builder/references/chatflow/nodes/human-review.md +0 -60
  27. package/skills/agent-builder/references/chatflow/nodes/if-else.md +0 -100
  28. package/skills/agent-builder/references/chatflow/nodes/intent.md +0 -79
  29. package/skills/agent-builder/references/chatflow/nodes/knowledge-reply.md +0 -43
  30. package/skills/agent-builder/references/chatflow/nodes/llm.md +0 -117
  31. package/skills/agent-builder/references/chatflow/nodes/note.md +0 -38
  32. package/skills/agent-builder/references/chatflow/nodes/question.md +0 -97
  33. package/skills/agent-builder/references/chatflow/nodes/rag-reply.md +0 -68
  34. package/skills/agent-builder/references/chatflow/nodes/reply.md +0 -97
  35. package/skills/agent-builder/references/chatflow/nodes/start.md +0 -35
  36. package/skills/agent-builder/references/chatflow/nodes/sub-graph.md +0 -48
  37. package/skills/agent-builder/references/chatflow/nodes/tool.md +0 -138
  38. package/skills/agent-builder/references/chatflow/nodes/transfer-to-human.md +0 -63
  39. package/skills/agent-builder/references/chatflow/nodes/variable-assignment.md +0 -52
  40. package/skills/agent-builder/references/chatflow/nodes/variable-extract.md +0 -50
  41. package/skills/agent-builder/references/chatflow/nodes/variable-process.md +0 -70
  42. package/skills/agent-builder/references/chatflow/overview.md +0 -53
  43. package/skills/agent-builder/references/chatflow/visual-translation.md +0 -64
  44. package/skills/agent-builder/references/chatflow/workflow.md +0 -134
  45. package/skills/agent-builder/references/multi-agent/create-update.md +0 -185
  46. package/skills/agent-builder/references/multi-agent/graph-spec.md +0 -246
  47. package/skills/agent-builder/references/multi-agent/graph-structure.md +0 -12
  48. package/skills/agent-builder/references/multi-agent/migrate-to-chatflow.md +0 -13
  49. package/skills/agent-builder/references/multi-agent/migration/large.md +0 -554
  50. package/skills/agent-builder/references/multi-agent/migration/principles.md +0 -446
  51. package/skills/agent-builder/references/multi-agent/migration/small.md +0 -225
  52. package/skills/agent-builder/references/multi-agent/orchestration.md +0 -11
  53. package/skills/agent-builder/references/multi-agent/overview.md +0 -48
  54. package/skills/agent-builder/references/multi-agent/update.md +0 -128
  55. package/skills/agent-builder/references/multi-agent/workflow.md +0 -87
  56. package/skills/agent-builder/references/single-agent/overview.md +0 -48
  57. package/skills/agent-builder/references/single-agent/prompt.md +0 -16
  58. package/skills/agent-builder/references/single-agent/tools.md +0 -36
  59. package/skills/agent-builder/references/single-agent/voice.md +0 -46
  60. package/skills/agent-builder/references/tools/http-tool.md +0 -315
  61. package/skills/agent-builder/references/tools/mock-tool.md +0 -279
  62. package/skills/agent-builder/references/tools/overview.md +0 -46
  63. package/skills/agent-builder/references/tools/tool-config.md +0 -43
  64. package/skills/agent-builder/references/tools/x-token.md +0 -34
  65. package/skills/agent-builder/scripts/chatflow/chatflow_builder.py +0 -3458
  66. package/skills/agent-builder/scripts/chatflow/validate_graph.py +0 -1697
  67. package/skills/agent-builder/scripts/copy_marketplace_agent.py +0 -61
  68. package/skills/agent-builder/scripts/multi-agent/legacy_graph_analyzer.py +0 -5974
  69. package/skills/agent-builder/scripts/multi-agent/replay_compare.py +0 -679
  70. package/skills/agent-builder/scripts/patch_agent_tools.py +0 -135
  71. package/skills/agent-builder/scripts/tools/validate_tool_config.py +0 -384
  72. package/skills/agent-tester/SKILL.md +0 -35
  73. package/skills/agent-tester/batch-test/overview.md +0 -86
  74. package/skills/agent-tester/scripts/batch_test_sheet.py +0 -213
  75. package/skills/agent-tester/single-test/badcase-retest.md +0 -85
  76. package/skills/agent-tester/single-test/direct-test.md +0 -24
  77. package/skills/agent-tester/single-test/overview.md +0 -142
  78. package/skills/delivery/SKILL.md +0 -112
  79. package/skills/delivery/interfaces/classify.md +0 -38
  80. package/skills/delivery/interfaces/create-agents.md +0 -89
  81. package/skills/delivery/interfaces/delivery-test.md +0 -67
  82. package/skills/delivery/references/flow.md +0 -40
  83. package/skills/delivery/references/validation.md +0 -15
  84. package/skills/delivery/scripts/batch_test.py +0 -704
  85. package/skills/delivery/scripts/classify.py +0 -22
  86. package/skills/delivery/scripts/create_agents.py +0 -695
  87. package/skills/delivery/scripts/gen_delivery_report.py +0 -335
  88. package/skills/delivery/scripts/gen_excel.py +0 -234
  89. package/skills/delivery/scripts/gen_report.py +0 -208
  90. package/skills/delivery/scripts/plan.py +0 -163
  91. package/skills/delivery/scripts/render.py +0 -713
  92. package/skills/delivery/scripts/render_summary.py +0 -38
  93. package/skills/delivery/scripts/sample_history_cases.py +0 -109
  94. package/skills/kb-builder/SKILL.md +0 -80
  95. package/skills/kb-builder/interfaces/qa-upload.md +0 -55
  96. package/skills/kb-builder/interfaces/web-kb.md +0 -84
  97. package/skills/kb-builder/references/directory.md +0 -373
  98. package/skills/kb-builder/references/export.md +0 -282
  99. package/skills/kb-builder/references/isolation-keywords.md +0 -113
  100. package/skills/kb-builder/references/knowledge.md +0 -153
  101. package/skills/kb-builder/references/product.md +0 -52
  102. package/skills/kb-builder/references/qa-from-help-center.md +0 -58
  103. package/skills/kb-builder/references/qa-from-pdf.md +0 -94
  104. package/skills/kb-builder/references/qa-from-tickets.md +0 -168
  105. package/skills/kb-builder/references/qa-from-web.md +0 -98
  106. package/skills/kb-builder/references/qa-quality.md +0 -73
  107. package/skills/kb-builder/references/reply-template.md +0 -237
  108. package/skills/kb-builder/references/tag-isolation-migration.md +0 -61
  109. package/skills/kb-builder/references/tags.md +0 -45
  110. package/skills/kb-builder/references/upload.md +0 -130
  111. package/skills/kb-builder/scripts/batch_extract_ticket_qa.py +0 -333
  112. package/skills/kb-builder/scripts/build_directory_plan.py +0 -144
  113. package/skills/kb-builder/scripts/crawl_site.py +0 -433
  114. package/skills/kb-builder/scripts/create_dirs.py +0 -146
  115. package/skills/kb-builder/scripts/export_knowledge.py +0 -454
  116. package/skills/kb-builder/scripts/extract_ticket_qa.py +0 -441
  117. package/skills/kb-builder/scripts/extract_web_qa.py +0 -118
  118. package/skills/kb-builder/scripts/import_qa.py +0 -219
  119. package/skills/kb-builder/scripts/import_source_documents.py +0 -143
  120. package/skills/kb-builder/scripts/import_templates.py +0 -409
  121. package/skills/poc/SKILL.md +0 -125
  122. package/skills/poc/assets/default_manual.css +0 -279
  123. package/skills/poc/assets/notice_escalate.png +0 -0
  124. package/skills/poc/assets/notice_escalate2.png +0 -0
  125. package/skills/poc/assets/notice_new_chat.png +0 -0
  126. package/skills/poc/assets/poc_test_template.html +0 -645
  127. package/skills/poc/assets/poc_test_template.md +0 -104
  128. package/skills/poc/assets/step3_preview.png +0 -0
  129. package/skills/poc/assets/step4_order_note.png +0 -0
  130. package/skills/poc/interfaces/classify.md +0 -38
  131. package/skills/poc/interfaces/create-agents.md +0 -108
  132. package/skills/poc/interfaces/poc-test.md +0 -94
  133. package/skills/poc/references/flow.md +0 -47
  134. package/skills/poc/references/kb-directory.md +0 -22
  135. package/skills/poc/references/validation.md +0 -17
  136. package/skills/poc/scripts/classify.py +0 -22
  137. package/skills/poc/scripts/create_agents.py +0 -818
  138. package/skills/poc/scripts/gen_excel.py +0 -234
  139. package/skills/poc/scripts/gen_poc_manual.py +0 -249
  140. package/skills/poc/scripts/gen_report.py +0 -208
  141. package/skills/poc/scripts/inspect_chatflow.py +0 -209
  142. package/skills/poc/scripts/plan.py +0 -178
  143. package/skills/poc/scripts/poc_test.py +0 -495
  144. package/skills/poc/scripts/render.py +0 -713
  145. package/skills/poc/scripts/render_summary.py +0 -41
  146. package/skills/runtime-inspector/SKILL.md +0 -51
  147. package/skills/runtime-inspector/references/frontend-rendering.md +0 -126
  148. package/skills/runtime-inspector/references/model.md +0 -59
  149. package/skills/runtime-inspector/references/sandbox.md +0 -131
  150. package/skills/runtime-inspector/scripts/model_inspect.py +0 -68
  151. package/skills/runtime-inspector/scripts/prepare.py +0 -98
  152. package/skills/runtime-inspector/scripts/process.py +0 -84
  153. package/skills/runtime-inspector/scripts/read_state.py +0 -32
  154. package/skills/runtime-inspector/scripts/sandbox_inspect.py +0 -463
  155. package/skills/runtime-inspector/scripts/test_claude_proxy.py +0 -49
  156. package/skills/runtime-inspector/scripts/test_openai_proxy.py +0 -41
  157. package/skills/runtime-inspector/scripts/verify_persistence.py +0 -101
  158. package/skills/runtime-inspector/scripts/write_test.py +0 -41
  159. package/skills/setup/SKILL.md +0 -17
  160. package/skills/shulex-support/SKILL.md +0 -39
  161. package/skills/shulex-support/references/badcase-analysis/overview.md +0 -34
  162. package/skills/shulex-support/references/badcase-analysis/text/conversation-record.md +0 -105
  163. package/skills/shulex-support/references/badcase-analysis/text/core-flow.md +0 -89
  164. package/skills/shulex-support/references/badcase-analysis/text/log-query-rules.md +0 -36
  165. package/skills/shulex-support/references/badcase-analysis/text/m1-entry.md +0 -53
  166. package/skills/shulex-support/references/badcase-analysis/text/m10-chatflow-service.md +0 -259
  167. package/skills/shulex-support/references/badcase-analysis/text/m11-algorithm-service.md +0 -148
  168. package/skills/shulex-support/references/badcase-analysis/text/m12-quality-inspection.md +0 -57
  169. package/skills/shulex-support/references/badcase-analysis/text/m2-intent.md +0 -120
  170. package/skills/shulex-support/references/badcase-analysis/text/m3-strategy.md +0 -126
  171. package/skills/shulex-support/references/badcase-analysis/text/m4-aifield.md +0 -121
  172. package/skills/shulex-support/references/badcase-analysis/text/m5-agent.md +0 -208
  173. package/skills/shulex-support/references/badcase-analysis/text/m6-tool.md +0 -115
  174. package/skills/shulex-support/references/badcase-analysis/text/m7-reply.md +0 -72
  175. package/skills/shulex-support/references/badcase-analysis/text/m8-error.md +0 -92
  176. package/skills/shulex-support/references/badcase-analysis/text/m9-knowledge-search.md +0 -147
  177. package/skills/shulex-support/references/badcase-analysis/text/sls-query.md +0 -283
  178. package/skills/shulex-support/references/badcase-analysis/voice/core-flow.md +0 -77
  179. package/skills/shulex-support/references/badcase-analysis/voice/dependencies.md +0 -19
  180. package/skills/shulex-support/references/badcase-analysis/voice/platform-api.md +0 -23
  181. package/skills/shulex-support/references/badcase-analysis/voice/sls-playbook.md +0 -31
  182. package/skills/shulex-support/references/product-docs.md +0 -70
  183. package/skills/shulex-support/references/tickets.md +0 -184
  184. package/skills/shulex-support/scripts/support.py +0 -427
  185. package/skills/solvea-desk/SKILL.md +0 -39
  186. package/skills/solvea-desk/references/report/fields.md +0 -82
  187. package/skills/solvea-desk/references/report/report-design.md +0 -200
  188. package/skills/solvea-desk/references/report/report-template.html +0 -44
  189. package/skills/solvea-desk/references/report/report.css +0 -517
  190. package/skills/solvea-desk/references/report/workflow.md +0 -464
  191. package/skills/solvea-desk/references/tickets/query.md +0 -103
  192. package/skills/solvea-desk/references/tickets/tags.md +0 -83
  193. package/skills/solvea-desk/scripts/report/fetch_sources.py +0 -580
  194. package/skills/solvea-desk/scripts/report/render_report.py +0 -763
  195. package/skills/solvea-desk/scripts/tickets/desk_api.py +0 -73
  196. package/skills/solvea-desk/scripts/tickets/fetch_tickets.py +0 -403
  197. package/skills/solvea-desk/scripts/tickets/ticket_tags.py +0 -305
  198. package/skills/solvea-kit/SKILL.md +0 -84
  199. package/skills/solvea-kit/references/agent.md +0 -145
  200. package/skills/solvea-kit/references/chat.md +0 -244
  201. package/skills/solvea-kit/references/intent.md +0 -69
  202. package/skills/solvea-kit/references/plugin.md +0 -53
  203. package/skills/solvea-kit/references/quality-inspection.md +0 -75
  204. package/skills/solvea-kit/references/strategy.md +0 -62
  205. package/skills/solvea-kit/references/tagging.md +0 -231
  206. package/skills/solvea-kit/references/variables.md +0 -53
  207. package/skills/solvea-kit/scripts/generate_label_import_file.py +0 -151
  208. package/skills/solvea-kit/scripts/intent_classify.py +0 -843
@@ -1,1341 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Solvea API Client - Manage Solvea customer service agents via Solvea bot backend.
4
-
5
- Usage:
6
- python3 solvea_api.py <command> [options]
7
-
8
- Commands:
9
- get-project Get projectId and botId from token
10
- replace-mock-data Replace mock data by type (fully replaces old data for the same tenant and type)
11
- query-mock-data Query mock data by type (--name required)
12
- list-agents List all agents
13
- get-agent Get agent detail by ID
14
- get-chatflow-graph Get latest chatFlowGraph for a CHAT_FLOW agent (--agent-id required)
15
- create-agent Create a new agent
16
- update-agent Update an existing agent (requires full object)
17
- update-chatflow Save or publish an existing CHAT_FLOW graph (--agent-id required)
18
- list-plugins List available tools
19
- get-plugin Get tool detail by ID
20
- create-plugin Create a new tool
21
- update-plugin Update a tool (requires full object)
22
- delete-plugin Delete a tool
23
- run-plugin Test-run an HTTP tool without saving
24
- list-ai-fields List fields exposed by the CHAT_FLOW canvas
25
- list-agent-ai-fields List fields accepted by Agent range validation
26
- get-ai-field-guide Get Tars AI Field initialization state
27
- initialize-ai-fields Initialize Tars system AI Fields
28
- list-managed-ai-fields Page Tars AI Field definitions
29
- get-managed-ai-field Get one Tars AI Field definition
30
- create-managed-ai-field Create one Tars custom AI Field
31
- update-managed-ai-field Update one Tars AI Field
32
- delete-managed-ai-field Delete one unused Tars custom AI Field
33
- toggle-managed-ai-field Enable or disable one Tars AI Field
34
- get-quality-status Get project AI quality inspection status
35
- set-quality-status Enable or disable project AI quality inspection
36
- list-quality-rules List quality inspection rules
37
- get-quality-rule Get one system quality inspection rule
38
- update-quality-rule Update one system quality inspection rule
39
- create-custom-quality-rule Create one custom quality inspection rule
40
- get-custom-quality-rule Get one custom quality inspection rule
41
- update-custom-quality-rule Update one custom quality inspection rule
42
- delete-custom-quality-rule Delete one custom quality inspection rule
43
- list-quality-alert-rules List quality inspection alert rules
44
- get-quality-alert-rule Get one quality inspection alert rule
45
- create-quality-alert-rule Create one quality inspection alert rule
46
- update-quality-alert-rule Update one quality inspection alert rule
47
- delete-quality-alert-rule Delete one quality inspection alert rule
48
- list-quality-alert-messages Page quality inspection alert messages
49
- get-quality-alert-message Get one quality inspection alert message
50
- chat Send a chat message (single or multi-turn)
51
- chat-stream Send a chat message via SSE stream and return aggregated final event
52
- chat-async Submit an async chat message, returns AsyncReceipt
53
- get-async-result Poll async chat result by receipt
54
- list-intents List intents
55
- get-conversations-by-intent Fetch historical USER conversations filtered by intent name
56
- create-intent Create a new intent
57
- get-intent Get intent detail
58
- update-intent Update an intent
59
- toggle-intent Enable/disable an intent
60
- list-entities List strategy condition entities
61
- create-strategy Create a new strategy
62
- update-strategy Update a strategy
63
- toggle-strategy Enable/disable a strategy
64
- list-strategies List strategies (paged)
65
- list-templates List reply templates
66
- create-template Create one reply template
67
- update-template Update one reply template (partial; --id + changed fields)
68
- delete-templates Delete reply templates by IDs
69
- list-knowledge List knowledge items
70
- get-knowledge Get one knowledge item by ID
71
- get-knowledge-isolation-keywords Get isolation keywords bound to one knowledge item
72
- update-knowledge Update one knowledge item
73
- batch-update-knowledge Batch update knowledge items (content, title, directoryId, etc.)
74
- update-document-content Update FILE/WEBSITE body content
75
- create-product-knowledge Create one product knowledge item
76
- update-product-knowledge Update one product knowledge item
77
- batch-update-products Batch update product status (mainPush, enable/published)
78
- operate-knowledge Delete, move, publish, unpublish, show, or hide knowledge
79
- edit-knowledge-tags Add, replace, or remove tags on knowledge items
80
- list-knowledge-tags List configured knowledge tags
81
- upload-qa Upload QA knowledge
82
- upload-web Upload web knowledge
83
- list-directories List the KB directory tree (slim by default; --raw for full)
84
- create-folder Create a knowledge folder
85
- update-directory Rename a knowledge folder
86
- move-directory Move a knowledge folder
87
- delete-directory Recursively delete a knowledge folder
88
- list-isolation-keywords List isolation keywords (paged)
89
- create-isolation-keyword Create one or more comma-separated isolation keywords
90
- update-isolation-keyword Update one isolation keyword
91
- delete-isolation-keyword Delete one isolation keyword
92
- batch-update-isolation-keyword-bindings Bind or revise keyword bindings for knowledge/directories
93
- list-transcripts Filter inbox conversation records
94
- get-transcript Get one conversation record by chatId
95
- resolve-transcript-chat-id Resolve an external inbox conversation ID to chatId
96
- get-chat-context Compare one turn's saved context with current resources
97
- export-transcripts Submit async transcript export task
98
- get-export-tasks Poll export task status
99
- upload-batch-test-file Upload a generated xlsx through temporary S3 credentials
100
- submit-batch-test Create a Download Center REPLY or MULTI_TURN_REPLY test task
101
- get-batch-test-tasks Query batch-test progress in Download Center
102
- list-label-groups List label groups
103
- create-label-group Create one label group
104
- update-label-group Update one label group
105
- delete-label-group Delete one label group
106
- get-label-tree Query one label tree
107
- get-label-detail-tree Query label node details
108
- create-label-node Create one label node
109
- update-label-node Update one label node
110
- delete-label-node Delete one label node
111
- download-label-template Download label import template
112
- download-labels Download current labels of one group
113
- upload-labels Upload local xlsx and import labels
114
- report-transcript Ticket-level stats (resolution/escalation rate)
115
- report-message Message-level stats and trend
116
- report-agent Agent performance stats
117
- report-intent Intent-level ticket stats
118
- report-satisfaction Satisfaction summary (AI vs Human)
119
- report-satisfaction-list Satisfaction detail list (paged)
120
- report-knowledge Knowledge usage stats
121
- report-escalation Escalation reason tree
122
- report-livechat LiveChat pre-sales funnel summary
123
- report-livechat-table LiveChat conversion detail table
124
- report-inspection AI inspection overview metrics
125
- report-self-learning Self-learning topic stats
126
- chat-report-* Modern /chatReport page metrics (default Agent Analytics source)
127
- list-voice-agents List voice agents
128
- list-voice-transcripts List voice call transcript pages
129
- get-voice-transcript Get voice call transcript detail
130
-
131
- Environment:
132
- SOLVEA_TOKEN JWT token for staging (_token_staging cookie)
133
- SOLVEA_BASE_URL Base URL (default: https://apps-staging.voc.ai)
134
- SOLVEA_PROJECT_ID Project ID (default: auto-detect or 950)
135
- SOLVEA_PROD_TOKEN JWT token for production (_token cookie)
136
- SOLVEA_PROD_URL Production URL (default: https://apps.voc.ai)
137
- """
138
-
139
- import json
140
- import os
141
- import sys
142
- import argparse
143
-
144
- from client import SolveaClient
145
-
146
-
147
- def summarize_knowledge_result(result, include_tags=False):
148
- items = result.get("list", result) if isinstance(result, dict) else result
149
- if not isinstance(items, list):
150
- return result
151
-
152
- compact = []
153
- for k in items:
154
- item = {
155
- "id": k.get("knowledgeId"),
156
- "directoryId": k.get("directoryId"),
157
- "type": k.get("type", ""),
158
- "status": k.get("knowledgeStatus", k.get("status", "")),
159
- "isolationKeywords": k.get("isolationKeywords", []),
160
- }
161
- # getKnowledge returns one unified DTO, but each UI type populates a
162
- # different subset. Preserve the useful fields instead of forcing
163
- # FILE/WEBSITE/PRODUCT into the QA question/answer shape.
164
- for field in (
165
- "question", "answer", "name", "title", "url", "brief", "storageSize",
166
- "productTitle", "productId", "sku", "spuUrl", "describe",
167
- ):
168
- if k.get(field) is not None:
169
- item[field] = k[field]
170
- if include_tags:
171
- item["tags"] = k.get("tags", [])
172
- compact.append(item)
173
- if isinstance(result, dict):
174
- return {
175
- "total": result.get("total", result.get("totalCount", len(compact))),
176
- "list": compact,
177
- }
178
- return compact
179
-
180
-
181
- def main():
182
- parser = argparse.ArgumentParser(
183
- description="Solvea API Client",
184
- formatter_class=argparse.RawDescriptionHelpFormatter,
185
- epilog=__doc__,
186
- )
187
- parser.add_argument("command", help="API command to execute")
188
- parser.add_argument("--id", help="Resource ID (agent/intent/strategy/etc)")
189
- parser.add_argument("--ids", nargs="+", type=int, help="Resource IDs for batch commands")
190
- parser.add_argument("--data", help="JSON data string")
191
- parser.add_argument("--data-file", help="JSON data file path")
192
- parser.add_argument("--message", "-m", help="Chat message")
193
- parser.add_argument("--chat-id", help="Chat ID for multi-turn")
194
- parser.add_argument("--external-id", help="External inbox conversation ID, e.g. ZENDESK-store-123")
195
- parser.add_argument("--bot-id", help="Bot ID for chat")
196
- parser.add_argument("--category", default=None, help="Category filter")
197
- parser.add_argument("--name", help="Resource name; MockData commands use it as mock_type")
198
- parser.add_argument("--content", help="Content for create-template")
199
- parser.add_argument("--page", type=int, default=1, help="Page index")
200
- parser.add_argument("--page-size", type=int, default=50, help="Page size")
201
- parser.add_argument("--keyword", default="", help="Search keyword")
202
- parser.add_argument("--status", default=None, help="Status filter/value for reply templates, e.g. ENABLED")
203
- parser.add_argument("--scene", action="append", default=None,
204
- help="Reply template scene filter/value. Can be repeated for list-templates.")
205
- parser.add_argument("--enable", action="store_true", help="Enable resource")
206
- parser.add_argument("--disable", action="store_true", help="Disable resource")
207
- parser.add_argument("--touch-point", default=None, help="Touch point filter")
208
- parser.add_argument("--urls", nargs="+", help="URLs for web knowledge")
209
- parser.add_argument("--parent-id", type=int, default=None, help="Parent folder ID")
210
- parser.add_argument("--max-depth", type=int, default=3,
211
- help="Maximum directory depth for create/move operations (default: 3)")
212
- parser.add_argument("--tree-id", type=int, help="label group/tree ID")
213
- parser.add_argument("--node-ids", nargs="+", type=int, help="label node IDs")
214
- parser.add_argument("--node-names", nargs="+", help="label node names")
215
- parser.add_argument("--parent-ids", nargs="+", type=int, help="label parent node IDs")
216
- parser.add_argument("--template-type", choices=["init", "update"],
217
- default="update", help="label template type")
218
- parser.add_argument("--output", help="Output file path for download commands")
219
- parser.add_argument("--init", action="store_true",
220
- help="Import labels with uploadWay=INIT for an empty label group")
221
- parser.add_argument("--debug", action="store_true", help="Include debug data")
222
- parser.add_argument("--time-offset", type=int, default=8, help="Timezone offset in hours")
223
- parser.add_argument("--begin-ts", type=int, help="Begin timestamp (ms)")
224
- parser.add_argument("--end-ts", type=int, help="End timestamp (ms)")
225
- parser.add_argument("--handoff", choices=["true", "false"], default=None,
226
- help="Transcript handoff filter: true or false")
227
- parser.add_argument("--agent-id", type=int, help="Agent ID")
228
- parser.add_argument("--intent-id", type=int, help="Intent ID for report-intent detail")
229
- parser.add_argument("--date-unit", default=None, help="Report date unit: DAY / WEEK / TOTAL")
230
- parser.add_argument("--sort-by", default=None, help="Report sort field JSON, e.g. '{\"TOTAL\":\"DESCEND\"}'")
231
- parser.add_argument("--assistant-role", default=None, help="Satisfaction role: ASSISTANT or HUMAN")
232
- parser.add_argument("--satisfied", default=None, choices=["true", "false"],
233
- help="Satisfaction filter: true or false")
234
- parser.add_argument("--start-date", default=None, help="Start date YYYY-MM-DD (inspection reports)")
235
- parser.add_argument("--end-date", default=None, help="End date YYYY-MM-DD (inspection reports)")
236
- parser.add_argument("--channel", default=None, help="Agent Dashboard channel filter")
237
- parser.add_argument("--business-type", default=None, help="Agent Dashboard businessType filter")
238
- parser.add_argument("--granularity", choices=["day", "week"], default=None,
239
- help="Agent Dashboard trend granularity")
240
- parser.add_argument("--week-start-date", default=None, help="Dashboard handoff week YYYY-MM-DD")
241
- parser.add_argument("--primary-reason", default=None, help="Dashboard handoff primary reason")
242
- parser.add_argument("--secondary-reason", default=None, help="Dashboard handoff secondary reason")
243
- parser.add_argument("--dimension", default="ticket", help="Inspection dimension (default: ticket)")
244
- parser.add_argument("--source", default=None, help="Knowledge source filter")
245
- parser.add_argument("--project-id", help="Project ID (overrides env var and auto-detection)")
246
- parser.add_argument("--directory-id", type=int, default=None,
247
- help="Knowledge directory ID filter (list-knowledge)")
248
- parser.add_argument("--include-tags", action="store_true",
249
- help="Opt in to include knowledge tags in list-knowledge summary; not sent to the API")
250
- parser.add_argument("--type", dest="knowledge_type", default="ALL",
251
- help="Knowledge type filter: ALL / QA / FILE / WEBSITE / PRODUCT / LEARN_SELF")
252
- parser.add_argument("--raw", action="store_true",
253
- help="Return raw API response for commands that normally summarize output")
254
- parser.add_argument("--all", action="store_true",
255
- help="Fetch all pages where supported")
256
- parser.add_argument("--env", choices=["staging", "prod"],
257
- default=os.environ.get("SOLVEA_ENV", "prod"),
258
- help="Environment: prod (default) or staging")
259
- parser.add_argument("--history-id", help="Voice transcript history ID")
260
- parser.add_argument("--receipt", help="AsyncReceipt for get-async-result")
261
- parser.add_argument("--callback", help="Callback URL for chat-async")
262
- parser.add_argument("--file", help="Local xlsx path for upload-batch-test-file")
263
- parser.add_argument("--file-key", help="S3 object key returned by upload-batch-test-file")
264
- parser.add_argument("--task-type", choices=["REPLY", "MULTI_TURN_REPLY"],
265
- help="Download Center batch-test task type")
266
- parser.add_argument("--expire-minutes", type=int,
267
- help="Expire minutes for chat-async receipt")
268
- parser.add_argument("--intent", help="Intent name (get-conversations-by-intent)")
269
- parser.add_argument("--inspection-key", help="Quality inspection rule key")
270
- parser.add_argument("--limit", type=int, default=50,
271
- help="Result limit (get-conversations-by-intent)")
272
- parser.add_argument("--pretty", action="store_true", help="Pretty print JSON")
273
- parser.add_argument("--publish", action="store_true",
274
- help="Publish update-chatflow instead of saving a draft")
275
- parser.add_argument("--confirm-delete", action="store_true",
276
- help="Required safety acknowledgement for destructive delete commands")
277
- args = parser.parse_args()
278
-
279
- client = SolveaClient(env=args.env, project_id=args.project_id)
280
- handoff = None if args.handoff is None else args.handoff == "true"
281
-
282
- def load_data():
283
- if args.data:
284
- return json.loads(args.data)
285
- if args.data_file:
286
- with open(args.data_file) as f:
287
- return json.load(f)
288
- return None
289
-
290
- def output(result):
291
- if args.pretty:
292
- print(json.dumps(result, indent=2, ensure_ascii=False))
293
- else:
294
- print(json.dumps(result, ensure_ascii=False))
295
-
296
- def update_chatflow():
297
- if not args.agent_id:
298
- raise ValueError("update-chatflow requires --agent-id")
299
- graph = load_data()
300
- if graph is None:
301
- raise ValueError("update-chatflow requires --data or --data-file")
302
- return client.update_chatflow(args.agent_id, graph, publish=args.publish)
303
-
304
- def replace_mock_data():
305
- if not args.name:
306
- raise ValueError("replace-mock-data requires --name")
307
- records = load_data()
308
- if not isinstance(records, list):
309
- raise ValueError("replace-mock-data requires --data or --data-file with a JSON array")
310
- return client.replace_mock_data(args.name, records)
311
-
312
- def query_mock_data():
313
- if not args.name:
314
- raise ValueError("query-mock-data requires --name")
315
- return client.query_mock_data(args.name)
316
-
317
- def run_plugin():
318
- data = load_data()
319
- if isinstance(data, dict) and "externalHttpCallConfig" in data:
320
- return client.run_http_plugin(
321
- data["externalHttpCallConfig"],
322
- data.get("externalHttpCallRequestBody"),
323
- )
324
- return client.run_http_plugin(data)
325
-
326
- def require_object_data(command):
327
- data = load_data()
328
- if not isinstance(data, dict):
329
- raise ValueError(f"{command} requires --data or --data-file with a JSON object")
330
- return data
331
-
332
- def set_quality_status():
333
- if args.enable == args.disable:
334
- raise ValueError("set-quality-status requires exactly one of --enable or --disable")
335
- return client.update_quality_inspection_status(args.enable)
336
-
337
- def require_inspection_key(command):
338
- if not args.inspection_key:
339
- raise ValueError(f"{command} requires --inspection-key")
340
- return args.inspection_key
341
-
342
- cmd = args.command.replace("-", "_")
343
-
344
- # ── Summary helpers: CLI output must stay under ~200 lines to avoid Bash tool truncation ──
345
-
346
- def summary_agents():
347
- agents = client.list_agents(category=args.category or "text")
348
- if isinstance(agents, list):
349
- return [{"id": a.get("id"), "name": a.get("name"), "type": a.get("type"),
350
- "description": a.get("description", "")} for a in agents]
351
- return agents
352
-
353
- def summary_plugins():
354
- plugins = client.list_plugins()
355
- if isinstance(plugins, list):
356
- return [{"id": p.get("id"), "name": p.get("name"), "type": p.get("type"),
357
- "description": p.get("description", ""), "visible": p.get("visible")}
358
- for p in plugins]
359
- return plugins
360
-
361
- def summary_intents():
362
- result = client.list_intents(category=args.category or "E_COMMERCE")
363
- intents = result if isinstance(result, list) else result.get("data", result.get("list", []))
364
- if isinstance(intents, list):
365
- return [{"id": i.get("id"), "name": i.get("name"), "status": i.get("status"),
366
- "description": i.get("example") or i.get("description", "")} for i in intents]
367
- return result
368
-
369
- def summary_knowledge():
370
- result = client.list_knowledge(
371
- page_index=args.page,
372
- page_size=args.page_size,
373
- keyword=args.keyword,
374
- knowledge_type=args.knowledge_type,
375
- directory_id=args.directory_id,
376
- )
377
- if args.raw:
378
- return result
379
- return summarize_knowledge_result(result, include_tags=args.include_tags)
380
-
381
- def summary_directories():
382
- # 默认精简:目录树每节点只留 id/name/kuCount/children + 顶层 totalKuCount;--raw 出完整结构
383
- result = client.list_directories()
384
- if args.raw:
385
- return result
386
-
387
- def slim(node):
388
- return {
389
- "id": node.get("id"),
390
- "name": node.get("name"),
391
- "kuCount": node.get("kuCount"),
392
- "children": [slim(c) for c in (node.get("children") or [])],
393
- }
394
-
395
- if isinstance(result, list):
396
- return [slim(n) for n in result]
397
- if isinstance(result, dict):
398
- key = next((k for k in ("list", "records", "data") if isinstance(result.get(k), list)), None)
399
- out = {}
400
- if result.get("totalKuCount") is not None:
401
- out["totalKuCount"] = result.get("totalKuCount")
402
- out["list"] = [slim(n) for n in result.get(key, [])] if key else []
403
- return out
404
- return result
405
-
406
- def summary_templates():
407
- result = client.list_templates(
408
- page_index=args.page,
409
- page_size=args.page_size,
410
- keyword=args.keyword,
411
- scenes=args.scene,
412
- status=[args.status] if args.status else None,
413
- )
414
- items = result.get("list", result) if isinstance(result, dict) else result
415
- if isinstance(items, list):
416
- compact = [
417
- {
418
- "id": item.get("id"),
419
- "name": item.get("name"),
420
- "content": item.get("content", ""),
421
- "status": item.get("status", ""),
422
- "scene": item.get("scene", ""),
423
- }
424
- for item in items
425
- ]
426
- if isinstance(result, dict):
427
- return {"total": result.get("total", result.get("totalCount", len(compact))), "list": compact}
428
- return compact
429
- return result
430
-
431
- def create_template():
432
- data = load_data() or {}
433
- if args.name:
434
- data["name"] = args.name
435
- if args.content:
436
- data["content"] = args.content
437
- if args.status:
438
- data["status"] = args.status
439
- if args.scene:
440
- data["scene"] = args.scene[0]
441
- if not data.get("name") or not data.get("content"):
442
- raise ValueError("create-template requires --name/--content or --data/--data-file with name and content")
443
- return client.create_template(data)
444
-
445
- def update_template():
446
- if not args.id:
447
- raise ValueError("update-template requires --id (templateId)")
448
- data = load_data() or {}
449
- # 只传要改的字段;multiContent 走 --data/--data-file(含多语种时)
450
- return client.update_template(
451
- args.id,
452
- name=args.name if args.name else data.get("name"),
453
- content=args.content if args.content else data.get("content"),
454
- status=args.status if args.status else data.get("status"),
455
- multi_content=data.get("multiContent"),
456
- )
457
-
458
- def delete_templates():
459
- if not args.confirm_delete:
460
- raise ValueError("delete-templates requires --confirm-delete after explicit user acknowledgement")
461
- data = load_data() or {}
462
- template_ids = args.ids or data.get("templateIds") or data.get("ids")
463
- if not template_ids:
464
- raise ValueError("delete-templates requires --ids or --data/--data-file with templateIds")
465
- return client.delete_templates(template_ids)
466
-
467
- def require_tree_id(command):
468
- if not args.tree_id:
469
- raise ValueError(f"{command} requires --tree-id")
470
- return args.tree_id
471
-
472
- def require_id(command):
473
- if not args.id:
474
- raise ValueError(f"{command} requires --id")
475
- return args.id
476
-
477
- def create_label_group():
478
- data = load_data() or {}
479
- tree_name = args.name or data.get("treeName")
480
- if not tree_name:
481
- raise ValueError("create-label-group requires --name or --data/--data-file with treeName")
482
- return client.create_label_group(
483
- tree_name,
484
- tree_describe=data.get("treeDescribe", ""),
485
- condition=data.get("condition"),
486
- is_multi_tag=data.get("isMultiTag"),
487
- )
488
-
489
- def update_label_group():
490
- data = load_data() or {}
491
- return client.update_label_group(
492
- require_id("update-label-group"),
493
- tree_name=args.name if args.name else data.get("treeName"),
494
- tree_describe=data.get("treeDescribe"),
495
- condition=data.get("condition"),
496
- is_multi_tag=data.get("isMultiTag"),
497
- )
498
-
499
- def get_label_tree():
500
- return client.get_label_tree(require_tree_id("get-label-tree"), name=args.name or args.keyword)
501
-
502
- def get_label_detail_tree():
503
- return client.get_label_detail_tree(
504
- require_tree_id("get-label-detail-tree"),
505
- node_ids=args.node_ids,
506
- node_names=args.node_names,
507
- parent_ids=args.parent_ids,
508
- )
509
-
510
- def create_label_node():
511
- data = load_data() or {}
512
- node_name = args.name or data.get("nodeName")
513
- describe = args.content if args.content is not None else data.get("describe", "")
514
- if not node_name:
515
- raise ValueError("create-label-node requires --name or --data/--data-file with nodeName")
516
- return client.create_label_node(
517
- require_tree_id("create-label-node"),
518
- node_name,
519
- describe,
520
- parent_id=data.get("parentId", args.parent_id),
521
- example_list=data.get("exampleList") or [],
522
- is_tag=data.get("isTag", True),
523
- )
524
-
525
- def update_label_node():
526
- data = load_data() or {}
527
- return client.update_label_node(
528
- require_tree_id("update-label-node"),
529
- require_id("update-label-node"),
530
- node_name=args.name if args.name else data.get("nodeName"),
531
- describe=args.content if args.content is not None else data.get("describe"),
532
- parent_id=data.get("parentId"),
533
- example_list=data.get("exampleList"),
534
- is_tag=data.get("isTag"),
535
- )
536
-
537
- def delete_label_node():
538
- tree_id = require_tree_id("delete-label-node")
539
- node_id = require_id("delete-label-node")
540
- return client.delete_label_node(
541
- tree_id,
542
- node_id,
543
- )
544
-
545
- def download_label_template():
546
- output_path = args.output or f"{args.template_type.lower()}_template.xlsx"
547
- template_type = "VOC_LABEL_INIT" if args.template_type == "init" else "VOC_LABEL"
548
- return {"file": client.download_label_template(template_type, output_path)}
549
-
550
- def download_labels():
551
- tree_id = require_tree_id("download-labels")
552
- output_path = args.output or f"labels_{tree_id}.xlsx"
553
- return {"file": client.download_labels(tree_id, output_path)}
554
-
555
- def upload_labels():
556
- tree_id = require_tree_id("upload-labels")
557
- if not args.file:
558
- raise ValueError("upload-labels requires --file")
559
- return client.upload_label_file(tree_id, args.file, init=args.init)
560
-
561
- def get_knowledge():
562
- if not args.id:
563
- raise ValueError("get-knowledge requires --id")
564
- if args.knowledge_type == "ALL":
565
- raise ValueError("get-knowledge requires --type QA, LEARN_SELF, FILE, WEBSITE, or PRODUCT")
566
- return client.get_knowledge_by_type(int(args.id), args.knowledge_type)
567
-
568
- def get_knowledge_isolation_keywords():
569
- if not args.id:
570
- raise ValueError("get-knowledge-isolation-keywords requires --id")
571
- if args.knowledge_type == "ALL":
572
- raise ValueError("get-knowledge-isolation-keywords requires an explicit --type")
573
- return client.get_knowledge_isolation_keywords(int(args.id), args.knowledge_type)
574
-
575
- def update_knowledge():
576
- if not args.id:
577
- raise ValueError("update-knowledge requires --id")
578
- data = load_data()
579
- if not isinstance(data, dict):
580
- raise ValueError("update-knowledge requires --data or --data-file with a JSON object")
581
- payload = dict(data)
582
- if "directoryId" not in payload:
583
- current = client.get_knowledge(int(args.id))
584
- if isinstance(current, dict) and current.get("directoryId") is not None:
585
- payload["directoryId"] = current["directoryId"]
586
- return client.update_knowledge(int(args.id), payload)
587
-
588
- def batch_update_knowledge():
589
- data = load_data()
590
- items = data.get("list") if isinstance(data, dict) and "list" in data else data
591
- if not isinstance(items, list) or not items:
592
- raise ValueError("batch-update-knowledge requires a JSON array or object with list")
593
- formatted = []
594
- for item in items:
595
- if not isinstance(item, dict):
596
- raise ValueError("Every item in batch-update-knowledge must be a JSON object")
597
- row = dict(item)
598
- if "id" in row and "kuId" not in row:
599
- row["kuId"] = row.pop("id")
600
- if "kuId" not in row:
601
- raise ValueError("Every item requires kuId or id")
602
- if "directoryId" not in row:
603
- current = client.get_knowledge(int(row["kuId"]))
604
- if isinstance(current, dict) and current.get("directoryId") is not None:
605
- row["directoryId"] = current["directoryId"]
606
- formatted.append(row)
607
- return client.update_kus(formatted)
608
-
609
- def update_document_content():
610
- if not args.id:
611
- raise ValueError("update-document-content requires --id")
612
- knowledge_type = str(args.knowledge_type).upper()
613
- if knowledge_type not in {"FILE", "WEBSITE"}:
614
- raise ValueError("update-document-content requires --type FILE or WEBSITE")
615
- changes = require_object_data("update-document-content")
616
- unknown = set(changes) - {"fileText", "filePath", "title"}
617
- if unknown:
618
- raise ValueError(f"Unsupported document content fields: {', '.join(sorted(unknown))}")
619
- if changes.get("fileText") is None and changes.get("filePath") is None:
620
- raise ValueError("update-document-content requires fileText or filePath")
621
-
622
- current = client.get_document(int(args.id))
623
- if not isinstance(current, dict):
624
- raise ValueError(f"Could not read document {args.id} before update")
625
- payload = {
626
- "type": knowledge_type,
627
- "directoryId": current.get("directoryId"),
628
- "fileName": current.get("fileName") or current.get("name") or current.get("title"),
629
- "title": current.get("title") or current.get("fileName") or current.get("name"),
630
- "metadata": current.get("metadata") if isinstance(current.get("metadata"), dict) else {},
631
- }
632
- payload.update(changes)
633
- payload["type"] = knowledge_type
634
- return client.update_document_content(int(args.id), payload)
635
-
636
- def create_product_knowledge():
637
- data = require_object_data("create-product-knowledge")
638
- for field in ("title", "spuUrl"):
639
- if not data.get(field):
640
- raise ValueError(f"create-product-knowledge requires {field}")
641
- return client.create_product_knowledge(data)
642
-
643
- def update_product_knowledge():
644
- if not args.id:
645
- raise ValueError("update-product-knowledge requires --id")
646
- changes = require_object_data("update-product-knowledge")
647
- current = client.get_product_knowledge(int(args.id))
648
- if not isinstance(current, dict):
649
- raise ValueError(f"Could not read product knowledge {args.id} before update")
650
- allowed_fields = {
651
- "productId", "tags", "documentId", "title", "handle", "vendor",
652
- "category", "brand", "productStatus", "country", "spuUrl", "image",
653
- "platform", "currency", "price", "compareAtPrice", "describe", "source",
654
- "skuList", "directoryId", "published", "metadata", "specialTagMap",
655
- "isVisible", "optionName",
656
- }
657
- unknown = set(changes) - allowed_fields - {"kuProductId", "id"}
658
- if unknown:
659
- raise ValueError(f"Unsupported product fields: {', '.join(sorted(unknown))}")
660
- payload = {key: value for key, value in current.items() if key in allowed_fields}
661
- payload.update({key: value for key, value in changes.items() if key in allowed_fields})
662
- missing = [
663
- field for field in ("title", "describe", "spuUrl", "image", "source", "published", "isVisible")
664
- if payload.get(field) is None or payload.get(field) == ""
665
- ]
666
- if missing:
667
- raise ValueError(f"Product detail is incomplete: {', '.join(missing)}")
668
- return client.update_product_knowledge(int(args.id), payload)
669
-
670
- def batch_update_products():
671
- data = load_data()
672
- items = data.get("list") if isinstance(data, dict) and "list" in data else data
673
- if not isinstance(items, list) or not items:
674
- raise ValueError("batch-update-products requires a JSON array or object with list")
675
- formatted = []
676
- for item in items:
677
- if not isinstance(item, dict):
678
- raise ValueError("Every item in batch-update-products must be a JSON object")
679
- row = dict(item)
680
- if "kuProductId" in row and "id" not in row:
681
- row["id"] = row.pop("kuProductId")
682
- if "id" not in row:
683
- raise ValueError("Every item requires id or kuProductId")
684
- entry = {"id": int(row["id"])}
685
- if "mainPush" in row:
686
- entry["mainPush"] = bool(row["mainPush"])
687
- if "enable" in row:
688
- entry["enable"] = bool(row["enable"])
689
- elif "published" in row:
690
- entry["enable"] = bool(row["published"])
691
- formatted.append(entry)
692
- return client.update_ku_products(formatted)
693
-
694
- def directory_index():
695
- result = client.list_directories()
696
- if isinstance(result, list):
697
- roots = result
698
- elif isinstance(result, dict):
699
- roots = next(
700
- (result.get(key) for key in ("list", "records", "data") if isinstance(result.get(key), list)),
701
- [],
702
- )
703
- else:
704
- roots = []
705
- index = {}
706
-
707
- def walk(node, depth, parent_id):
708
- node_id = node.get("id")
709
- if node_id is None:
710
- return
711
- index[int(node_id)] = {"node": node, "depth": depth, "parentId": parent_id}
712
- for child in node.get("children") or []:
713
- walk(child, depth + 1, int(node_id))
714
-
715
- for root in roots:
716
- walk(root, 1, 0)
717
- return index
718
-
719
- def subtree_ids(index, directory_id):
720
- descendants = {directory_id}
721
- changed = True
722
- while changed:
723
- changed = False
724
- for node_id, item in index.items():
725
- if item["parentId"] in descendants and node_id not in descendants:
726
- descendants.add(node_id)
727
- changed = True
728
- return descendants
729
-
730
- def create_folder():
731
- if not args.name:
732
- raise ValueError("create-folder requires --name")
733
- if args.max_depth <= 0:
734
- raise ValueError("--max-depth must be positive")
735
- parent_id = args.parent_id if args.parent_id is not None else 0
736
- if parent_id:
737
- index = directory_index()
738
- if parent_id not in index:
739
- raise ValueError(f"Parent directory {parent_id} was not found")
740
- if index[parent_id]["depth"] + 1 > args.max_depth:
741
- raise ValueError(f"Directory would exceed maximum depth {args.max_depth}")
742
- return client.create_folder(args.name, parent_id=parent_id)
743
-
744
- def update_directory():
745
- if not args.id:
746
- raise ValueError("update-directory requires --id")
747
- data = load_data() or {}
748
- if not isinstance(data, dict):
749
- raise ValueError("update-directory requires a JSON object")
750
- if args.name:
751
- data["name"] = args.name
752
- if not data.get("name"):
753
- raise ValueError("update-directory requires --name or data.name")
754
- index = directory_index()
755
- if int(args.id) not in index:
756
- raise ValueError(f"Directory {args.id} was not found")
757
- return client.update_directory(int(args.id), {"name": data["name"]})
758
-
759
- def move_directory():
760
- if not args.id:
761
- raise ValueError("move-directory requires --id")
762
- if args.parent_id is None:
763
- raise ValueError("move-directory requires --parent-id; use 0 for root")
764
- if args.max_depth <= 0:
765
- raise ValueError("--max-depth must be positive")
766
- directory_id = int(args.id)
767
- parent_id = int(args.parent_id)
768
- index = directory_index()
769
- if directory_id not in index:
770
- raise ValueError(f"Directory {directory_id} was not found")
771
- if parent_id and parent_id not in index:
772
- raise ValueError(f"Parent directory {parent_id} was not found")
773
- descendants = subtree_ids(index, directory_id)
774
- if parent_id in descendants:
775
- raise ValueError("A directory cannot be moved into itself or its descendants")
776
- current_depth = index[directory_id]["depth"]
777
- subtree_height = max(index[node_id]["depth"] - current_depth + 1 for node_id in descendants)
778
- parent_depth = index[parent_id]["depth"] if parent_id else 0
779
- if parent_depth + subtree_height > args.max_depth:
780
- raise ValueError(f"Moved subtree would exceed maximum depth {args.max_depth}")
781
- return client.move_directory(directory_id, parent_id)
782
-
783
- def delete_directory():
784
- if not args.id:
785
- raise ValueError("delete-directory requires --id")
786
- if not args.confirm_delete:
787
- raise ValueError("delete-directory requires --confirm-delete after explicit user acknowledgement")
788
- index = directory_index()
789
- if int(args.id) not in index:
790
- raise ValueError(f"Directory {args.id} was not found")
791
- return client.delete_directory(int(args.id))
792
-
793
- def operate_knowledge():
794
- data = load_data()
795
- request_list = data.get("requestList") if isinstance(data, dict) else data
796
- if not isinstance(request_list, list) or not request_list:
797
- raise ValueError("operate-knowledge requires a JSON array or object with requestList")
798
- supported_types = {"QA", "LEARN_SELF", "FILE", "WEBSITE", "PRODUCT"}
799
- supported_operations = {"DELETE", "MOVE", "PUBLISH", "UNPUBLISH", "VISIBLE", "INVISIBLE"}
800
- normalized = []
801
- move_directory_ids = set()
802
- for item in request_list:
803
- if not isinstance(item, dict):
804
- raise ValueError("Every knowledge operation must be an object")
805
- knowledge_type = str(item.get("knowledgeType", "")).upper()
806
- operation = str(item.get("operateType", "")).upper()
807
- if knowledge_type not in supported_types:
808
- raise ValueError(f"Unsupported knowledge type: {knowledge_type}")
809
- if operation not in supported_operations:
810
- raise ValueError(f"Unsupported knowledge operation: {operation}")
811
- try:
812
- knowledge_id = int(item["knowledgeId"])
813
- except (KeyError, TypeError, ValueError):
814
- raise ValueError("Every knowledge operation requires a numeric knowledgeId") from None
815
- normalized_item = {
816
- "knowledgeType": knowledge_type,
817
- "knowledgeId": knowledge_id,
818
- "operateType": operation,
819
- }
820
- if operation == "MOVE":
821
- try:
822
- normalized_item["directoryId"] = int(item["directoryId"])
823
- except (KeyError, TypeError, ValueError):
824
- raise ValueError("MOVE requires a numeric directoryId") from None
825
- move_directory_ids.add(normalized_item["directoryId"])
826
- client.get_knowledge_by_type(knowledge_id, knowledge_type)
827
- normalized.append(normalized_item)
828
- if any(item["operateType"] == "DELETE" for item in normalized) and not args.confirm_delete:
829
- raise ValueError("DELETE requires --confirm-delete after explicit user acknowledgement")
830
- if move_directory_ids:
831
- index = directory_index()
832
- missing_directories = sorted(directory_id for directory_id in move_directory_ids if directory_id not in index)
833
- if missing_directories:
834
- raise ValueError(f"Target directories were not found: {missing_directories}")
835
- return client.operate_knowledge(normalized)
836
-
837
- def edit_knowledge_tags():
838
- data = require_object_data("edit-knowledge-tags")
839
- knowledge_ids = data.get("knowledgeIds")
840
- requested_tags = data.get("tagList")
841
- if not isinstance(knowledge_ids, list) or not knowledge_ids:
842
- raise ValueError("edit-knowledge-tags requires non-empty knowledgeIds")
843
- if not isinstance(requested_tags, list):
844
- raise ValueError("edit-knowledge-tags requires tagList array")
845
- operation = data.get("operation", "append")
846
- if operation not in {"append", "replace", "remove"}:
847
- raise ValueError("edit-knowledge-tags operation must be append, replace, or remove")
848
-
849
- knowledge_type = str(data.get("type", "QA")).upper()
850
- updates = []
851
- for knowledge_id in knowledge_ids:
852
- knowledge = client.get_knowledge_by_type(int(knowledge_id), knowledge_type)
853
- if not knowledge:
854
- raise ValueError(f"Could not read knowledge {knowledge_id} before tag update")
855
- current = knowledge.get("tags")
856
- if not isinstance(current, list):
857
- current = (knowledge.get("metadata") or {}).get("tags", [])
858
- if not isinstance(current, list):
859
- current = []
860
- if operation == "append":
861
- target = list(dict.fromkeys([*current, *requested_tags]))
862
- elif operation == "remove":
863
- target = [tag for tag in current if tag not in requested_tags]
864
- else:
865
- target = list(dict.fromkeys(requested_tags))
866
- updates.append(client.edit_knowledge_tags(
867
- knowledge_ids=[int(knowledge_id)],
868
- tag_list=target,
869
- knowledge_type=knowledge_type,
870
- ))
871
- return {"success": True, "results": updates}
872
-
873
- def summary_knowledge_tags():
874
- result = client.list_knowledge_tags(keyword=args.keyword)
875
- if args.raw:
876
- return result
877
- if isinstance(result, dict) and isinstance(result.get("metadataTagAll"), list):
878
- return {"tags": result["metadataTagAll"]}
879
- return result
880
-
881
- def summary_isolation_keywords():
882
- if args.all:
883
- return client.list_all_isolation_keywords(keyword=args.keyword, page_size=args.page_size)
884
- result = client.list_isolation_keywords(
885
- keyword=args.keyword,
886
- page_index=args.page,
887
- page_size=args.page_size,
888
- )
889
- if args.raw:
890
- return result
891
- items = result.get("list", result) if isinstance(result, dict) else result
892
- if not isinstance(items, list):
893
- return result
894
- compact = [
895
- {
896
- "id": item.get("id"),
897
- "keyword": item.get("keyword", ""),
898
- "bindCount": item.get("bindCount", 0),
899
- "projectId": item.get("projectId"),
900
- "tenantId": item.get("tenantId"),
901
- "createdAt": item.get("createdAt"),
902
- "updatedAt": item.get("updatedAt"),
903
- }
904
- for item in items
905
- ]
906
- return compact
907
-
908
- def create_isolation_keyword():
909
- if not args.keyword:
910
- raise ValueError("create-isolation-keyword requires --keyword")
911
- requested = list(dict.fromkeys(part.strip() for part in args.keyword.split(",") if part.strip()))
912
- if not requested:
913
- raise ValueError("create-isolation-keyword requires at least one non-empty keyword")
914
- existing = client.list_all_isolation_keywords(page_size=args.page_size)
915
- existing_names = {str(item.get("keyword", "")).strip() for item in existing}
916
- duplicates = [name for name in requested if name in existing_names]
917
- pending = [name for name in requested if name not in existing_names]
918
- if not pending:
919
- return {"duplicate": duplicates, "failed": [], "success": [], "created": False}
920
- result = client.create_isolation_keyword(",".join(pending))
921
- if isinstance(result, dict):
922
- result = dict(result)
923
- result["duplicate"] = duplicates + list(result.get("duplicate", []))
924
- result["created"] = True
925
- return result
926
-
927
- def update_isolation_keyword():
928
- if not args.id:
929
- raise ValueError("update-isolation-keyword requires --id")
930
- if not args.keyword:
931
- raise ValueError("update-isolation-keyword requires --keyword")
932
- return client.update_isolation_keyword(int(args.id), args.keyword)
933
-
934
- def delete_isolation_keyword():
935
- if not args.id:
936
- raise ValueError("delete-isolation-keyword requires --id")
937
- if not args.confirm_delete:
938
- raise ValueError("delete-isolation-keyword requires --confirm-delete after explicit user acknowledgement")
939
- return client.delete_isolation_keyword(int(args.id))
940
-
941
- def batch_update_isolation_keyword_bindings():
942
- data = require_object_data("batch-update-isolation-keyword-bindings")
943
- operation = str(data.get("operation", "append")).lower()
944
- if operation not in {"append", "remove", "replace"}:
945
- raise ValueError("operation must be append, remove, or replace")
946
-
947
- items = data.get("items") or []
948
- directory_ids = data.get("directoryIds") or []
949
- if not items and not directory_ids:
950
- raise ValueError("binding update requires knowledge items or directory IDs")
951
- if directory_ids and operation != "replace":
952
- raise ValueError(
953
- "directory bindings require operation=replace because the backend replaces the final binding set"
954
- )
955
- if directory_ids and data.get("confirmReplace") is not True:
956
- raise ValueError(
957
- "directory binding replacement requires confirmReplace=true after explicit user confirmation"
958
- )
959
-
960
- resolved_items = []
961
- for item in items:
962
- knowledge_id = item.get("knowledgeId") if isinstance(item, dict) else None
963
- knowledge_type = (item.get("knowledgeType") if isinstance(item, dict) else None)
964
- knowledge_type = knowledge_type.upper() if isinstance(knowledge_type, str) else None
965
- if knowledge_id is None:
966
- raise ValueError("Could not read existing binding for every knowledge item")
967
- if knowledge_type is None:
968
- raise ValueError("items[].knowledgeType is required before binding isolation keywords")
969
- client.get_knowledge_by_type(int(knowledge_id), knowledge_type)
970
- current_bindings = client.get_knowledge_isolation_keywords(int(knowledge_id), knowledge_type)
971
- if not isinstance(current_bindings, list):
972
- raise ValueError("existing isolation keyword bindings must be an array")
973
- current_ids = []
974
- for binding in current_bindings:
975
- keyword_id = binding.get("id") if isinstance(binding, dict) else None
976
- if isinstance(keyword_id, bool) or not isinstance(keyword_id, int) or keyword_id <= 0:
977
- raise ValueError("existing isolation keyword bindings contain an invalid ID")
978
- current_ids.append(keyword_id)
979
-
980
- requested_ids = item.get("keywordIds") if isinstance(item, dict) else None
981
- if not isinstance(requested_ids, list):
982
- raise ValueError("items[].keywordIds must be an array")
983
- if any(
984
- isinstance(keyword_id, bool)
985
- or not isinstance(keyword_id, int)
986
- or keyword_id <= 0
987
- for keyword_id in requested_ids
988
- ):
989
- raise ValueError("items[].keywordIds must contain positive integer IDs")
990
- if operation == "append":
991
- final_ids = list(dict.fromkeys([*current_ids, *requested_ids]))
992
- elif operation == "remove":
993
- removed_ids = set(requested_ids)
994
- final_ids = [keyword_id for keyword_id in current_ids if keyword_id not in removed_ids]
995
- else:
996
- if data.get("confirmReplace") is not True:
997
- raise ValueError("binding replacement requires confirmReplace=true after explicit user confirmation")
998
- final_ids = list(dict.fromkeys(requested_ids))
999
- resolved_items.append({**item, "knowledgeType": knowledge_type, "keywordIds": final_ids})
1000
-
1001
- for directory_id in directory_ids:
1002
- client.list_knowledge(directory_id=int(directory_id), page_size=1000)
1003
- return client.batch_update_isolation_keyword_bindings(
1004
- items=resolved_items,
1005
- directory_ids=directory_ids,
1006
- keyword_ids=data.get("keywordIds"),
1007
- )
1008
-
1009
- def summary_transcripts():
1010
- result = client.list_transcripts(
1011
- page_size=args.page_size,
1012
- page_index=args.page,
1013
- debug=args.debug,
1014
- begin_ts=args.begin_ts,
1015
- end_ts=args.end_ts,
1016
- touch_point=args.touch_point,
1017
- bot_id=args.bot_id,
1018
- agent_id=args.agent_id,
1019
- intent_id=args.intent_id,
1020
- handoff=handoff,
1021
- keyword=args.keyword or None,
1022
- )
1023
- items = result.get("list", result) if isinstance(result, dict) else result
1024
- if isinstance(items, list):
1025
- compact = [{"chatId": t.get("chatId", t.get("id")),
1026
- "agentName": t.get("agentName", ""),
1027
- "touchPoint": t.get("touchPoint", t.get("touchpoint", "")),
1028
- "handoff": t.get("handoff"), "messageCount": t.get("messageCount"),
1029
- "externalId": t.get("externId", t.get("externalId", t.get("external_id"))),
1030
- "createdAt": t.get("createdAt", "")} for t in items]
1031
- if isinstance(result, dict):
1032
- return {
1033
- "pageIndex": result.get("pageIndex", args.page),
1034
- "pageSize": result.get("pageSize", args.page_size),
1035
- "hasNext": bool(result.get("hasNext")),
1036
- "list": compact,
1037
- }
1038
- return compact
1039
- return result
1040
-
1041
- def resolve_transcript_chat_id():
1042
- if not args.external_id:
1043
- raise ValueError("resolve-transcript-chat-id requires --external-id")
1044
- return client.resolve_transcript_chat_id(
1045
- args.external_id,
1046
- page_size=args.page_size,
1047
- debug=args.debug,
1048
- )
1049
-
1050
- def summary_voice_agents():
1051
- agents = client.list_voice_agents()
1052
- if isinstance(agents, list):
1053
- return [{"id": a.get("id"), "name": a.get("name"), "type": a.get("type"),
1054
- "description": a.get("description", "")} for a in agents]
1055
- return agents
1056
-
1057
- def summary_voice_transcripts():
1058
- result = client.list_voice_transcripts(
1059
- page_num=args.page, page_size=args.page_size,
1060
- begin_ts=args.begin_ts, end_ts=args.end_ts)
1061
- items = result.get("records", result.get("list", result)) if isinstance(result, dict) else result
1062
- if isinstance(items, list):
1063
- compact = [{"callId": t.get("callId", t.get("chatId", "")),
1064
- "agentId": t.get("agentId"),
1065
- "callerNumber": t.get("callerNumber", ""),
1066
- "callDuration": t.get("callDuration"),
1067
- "endReason": t.get("endReason", ""),
1068
- "createdAt": t.get("createdAt", "")} for t in items]
1069
-
1070
-
1071
-
1072
-
1073
- if isinstance(result, dict):
1074
- return {"total": result.get("total", len(compact)), "list": compact}
1075
- return compact
1076
- return result
1077
-
1078
- commands = {
1079
- "get_project": lambda: client.get_project_info(),
1080
- "replace_mock_data": replace_mock_data,
1081
- "query_mock_data": query_mock_data,
1082
- "list_agents": summary_agents,
1083
- "get_agent": lambda: client.get_agent(args.id),
1084
- "get_chatflow_graph": lambda: client.get_chatflow_graph(args.agent_id),
1085
- "create_agent": lambda: client.create_agent(load_data()),
1086
- "update_agent": lambda: client.update_agent(load_data()),
1087
- "update_chatflow": update_chatflow,
1088
- "list_plugins": summary_plugins,
1089
- "get_plugin": lambda: client.get_plugin(args.id),
1090
- "create_plugin": lambda: client.create_plugin(load_data()),
1091
- "update_plugin": lambda: client.update_plugin(load_data()),
1092
- "delete_plugin": lambda: client.delete_plugin(args.id),
1093
- "run_plugin": run_plugin,
1094
- "list_ai_fields": lambda: client.list_ai_fields(),
1095
- "list_agent_ai_fields": lambda: client.list_agent_ai_fields(),
1096
- "get_ai_field_guide": lambda: client.get_ai_field_guide_record(),
1097
- "initialize_ai_fields": lambda: client.initialize_ai_fields(),
1098
- "list_managed_ai_fields": lambda: client.list_managed_ai_fields(
1099
- page_index=args.page, page_size=args.page_size, name=args.keyword),
1100
- "get_managed_ai_field": lambda: client.get_managed_ai_field(args.id),
1101
- "create_managed_ai_field": lambda: client.create_managed_ai_field(
1102
- require_object_data("create-managed-ai-field")),
1103
- "update_managed_ai_field": lambda: client.update_managed_ai_field(
1104
- require_object_data("update-managed-ai-field")),
1105
- "delete_managed_ai_field": lambda: client.delete_managed_ai_field(args.id),
1106
- "toggle_managed_ai_field": lambda: client.toggle_managed_ai_field(args.id),
1107
- "get_quality_status": lambda: client.get_quality_inspection_status(),
1108
- "set_quality_status": set_quality_status,
1109
- "list_quality_rules": lambda: client.list_quality_inspection_rules(
1110
- begin_timestamp=args.begin_ts, end_timestamp=args.end_ts),
1111
- "get_quality_rule": lambda: client.get_quality_inspection_rule(
1112
- require_inspection_key("get-quality-rule")),
1113
- "update_quality_rule": lambda: client.update_quality_inspection_rule(
1114
- require_object_data("update-quality-rule")),
1115
- "create_custom_quality_rule": lambda: client.create_custom_quality_rule(
1116
- require_object_data("create-custom-quality-rule")),
1117
- "get_custom_quality_rule": lambda: client.get_custom_quality_rule(
1118
- require_inspection_key("get-custom-quality-rule")),
1119
- "update_custom_quality_rule": lambda: client.update_custom_quality_rule(
1120
- require_object_data("update-custom-quality-rule")),
1121
- "delete_custom_quality_rule": lambda: client.delete_custom_quality_rule(
1122
- require_inspection_key("delete-custom-quality-rule")),
1123
- "list_quality_alert_rules": lambda: client.list_quality_alert_rules(),
1124
- "get_quality_alert_rule": lambda: client.get_quality_alert_rule(args.id),
1125
- "create_quality_alert_rule": lambda: client.create_quality_alert_rule(
1126
- require_object_data("create-quality-alert-rule")),
1127
- "update_quality_alert_rule": lambda: client.update_quality_alert_rule(
1128
- require_object_data("update-quality-alert-rule")),
1129
- "delete_quality_alert_rule": lambda: client.delete_quality_alert_rule(args.id),
1130
- "list_quality_alert_messages": lambda: client.list_quality_alert_messages(
1131
- require_object_data("list-quality-alert-messages")),
1132
- "get_quality_alert_message": lambda: client.get_quality_alert_message(args.id),
1133
- "chat": lambda: client.chat(
1134
- message=args.message, bot_id=args.bot_id, chat_id=args.chat_id,
1135
- touch_point=args.touch_point or "LIVE_CHAT",
1136
- metadata=(load_data() or {}).get("metadata") if (args.data or args.data_file) else None),
1137
- "chat_stream": lambda: client.chat_stream(
1138
- message=args.message, bot_id=args.bot_id, chat_id=args.chat_id,
1139
- touch_point=args.touch_point or "LIVE_CHAT",
1140
- metadata=(load_data() or {}).get("metadata") if (args.data or args.data_file) else None),
1141
- "chat_async": lambda: client.chat_async(
1142
- message=args.message, bot_id=args.bot_id, chat_id=args.chat_id,
1143
- touch_point=args.touch_point or "LIVE_CHAT",
1144
- metadata=(load_data() or {}).get("metadata") if (args.data or args.data_file) else None,
1145
- callback=args.callback, expire_minutes=args.expire_minutes),
1146
- "get_async_result": lambda: client.get_async_result(
1147
- args.receipt, bot_id=args.bot_id),
1148
- "list_strategies": lambda: client.list_strategies(page_size=args.page_size),
1149
- "list_templates": summary_templates,
1150
- "create_template": create_template,
1151
- "update_template": update_template,
1152
- "delete_templates": delete_templates,
1153
- "get_conversations_by_intent": lambda: client.get_conversations_by_intent(
1154
- args.intent, limit=args.limit),
1155
- "list_intents": summary_intents,
1156
- "create_intent": lambda: client.create_intent(load_data()),
1157
- "get_intent": lambda: client.get_intent(args.id),
1158
- "update_intent": lambda: client.update_intent(args.id, load_data()),
1159
- "toggle_intent": lambda: client.toggle_intent(
1160
- args.id, enabled=not args.disable),
1161
- "list_entities": lambda: client.list_entities(),
1162
- "create_strategy": lambda: client.create_strategy(load_data()),
1163
- "update_strategy": lambda: client.update_strategy(args.id, load_data()),
1164
- "toggle_strategy": lambda: client.toggle_strategy(
1165
- args.id, enabled=not args.disable),
1166
- "list_knowledge": summary_knowledge,
1167
- "get_knowledge": get_knowledge,
1168
- "get_knowledge_isolation_keywords": get_knowledge_isolation_keywords,
1169
- "update_knowledge": update_knowledge,
1170
- "batch_update_knowledge": batch_update_knowledge,
1171
- "update_document_content": update_document_content,
1172
- "create_product_knowledge": create_product_knowledge,
1173
- "update_product_knowledge": update_product_knowledge,
1174
- "batch_update_products": batch_update_products,
1175
- "operate_knowledge": operate_knowledge,
1176
- "edit_knowledge_tags": edit_knowledge_tags,
1177
- "list_knowledge_tags": summary_knowledge_tags,
1178
- "upload_qa": lambda: client.upload_qa(load_data()),
1179
- "upload_web": lambda: client.upload_web(args.urls or []),
1180
- "list_directories": summary_directories,
1181
- "create_folder": create_folder,
1182
- "update_directory": update_directory,
1183
- "move_directory": move_directory,
1184
- "delete_directory": delete_directory,
1185
- "list_isolation_keywords": summary_isolation_keywords,
1186
- "create_isolation_keyword": create_isolation_keyword,
1187
- "update_isolation_keyword": update_isolation_keyword,
1188
- "delete_isolation_keyword": delete_isolation_keyword,
1189
- "batch_update_isolation_keyword_bindings": batch_update_isolation_keyword_bindings,
1190
- "list_transcripts": summary_transcripts,
1191
- "get_transcript": lambda: client.get_transcript(args.chat_id),
1192
- "resolve_transcript_chat_id": resolve_transcript_chat_id,
1193
- "get_chat_context": lambda: client.get_chat_context(
1194
- args.bot_id, json.loads(args.data) if args.data else {}
1195
- ),
1196
- "export_transcripts": lambda: client.export_transcripts(
1197
- page_size=args.page_size, page_index=args.page, debug=args.debug,
1198
- begin_ts=args.begin_ts, end_ts=args.end_ts,
1199
- time_offset=args.time_offset),
1200
- "get_export_tasks": lambda: client.get_export_tasks(
1201
- page=args.page, page_size=args.page_size),
1202
- "upload_batch_test_file": lambda: client.upload_batch_test_file(args.file),
1203
- "submit_batch_test": lambda: client.submit_batch_test(
1204
- args.file_key,
1205
- args.task_type,
1206
- bot_id=args.bot_id,
1207
- metadata=(load_data() or {}).get("metadata") if (args.data or args.data_file) else None,
1208
- ),
1209
- "get_batch_test_tasks": lambda: client.get_label_tasks(
1210
- page=args.page, page_size=args.page_size, task_type=args.task_type),
1211
- "list_label_groups": lambda: client.list_label_groups(tree_name=args.name or args.keyword),
1212
- "create_label_group": create_label_group,
1213
- "update_label_group": update_label_group,
1214
- "delete_label_group": lambda: client.delete_label_group(require_id("delete-label-group")),
1215
- "get_label_tree": get_label_tree,
1216
- "get_label_detail_tree": get_label_detail_tree,
1217
- "create_label_node": create_label_node,
1218
- "update_label_node": update_label_node,
1219
- "delete_label_node": delete_label_node,
1220
- "download_label_template": download_label_template,
1221
- "download_labels": download_labels,
1222
- "upload_labels": upload_labels,
1223
- "report_transcript": lambda: client.report_transcript(
1224
- args.begin_ts, args.end_ts, time_offset=args.time_offset,
1225
- bot_id=args.bot_id, touch_point=args.touch_point,
1226
- date_unit=args.date_unit),
1227
- "report_message": lambda: client.report_message(
1228
- args.begin_ts, args.end_ts, time_offset=args.time_offset,
1229
- bot_id=args.bot_id, touch_point=args.touch_point,
1230
- date_unit=args.date_unit),
1231
- "report_agent": lambda: client.report_agent(
1232
- args.begin_ts, args.end_ts, args.time_offset, args.bot_id, args.touch_point,
1233
- args.date_unit, args.agent_id),
1234
- "report_intent": lambda: client.report_intent(
1235
- args.begin_ts, args.end_ts, time_offset=args.time_offset,
1236
- bot_id=args.bot_id, touch_point=args.touch_point,
1237
- intent_id=args.intent_id, date_unit=args.date_unit),
1238
- "report_satisfaction": lambda: client.report_satisfaction_summary(
1239
- args.begin_ts, args.end_ts, args.time_offset, args.bot_id, args.touch_point),
1240
- "report_satisfaction_list": lambda: client.report_satisfaction_list(
1241
- args.begin_ts, args.end_ts, args.time_offset, args.page, args.page_size,
1242
- args.bot_id, args.touch_point, args.assistant_role,
1243
- None if args.satisfied is None else args.satisfied == "true"),
1244
- "report_knowledge": lambda: client.report_knowledge(
1245
- args.begin_ts, args.end_ts, args.time_offset, args.page, args.page_size,
1246
- json.loads(args.sort_by) if args.sort_by else None,
1247
- args.bot_id, args.touch_point, args.source),
1248
- "report_escalation": lambda: client.report_escalation_reasons(
1249
- args.begin_ts, args.end_ts, args.time_offset,
1250
- args.date_unit or "DAY", args.bot_id, args.touch_point),
1251
- "report_livechat": lambda: client.report_livechat_summary(
1252
- args.begin_ts, args.end_ts, args.time_offset),
1253
- "report_livechat_table": lambda: client.report_livechat_table(
1254
- args.begin_ts, args.end_ts, args.time_offset, args.page, args.page_size),
1255
- "report_inspection": lambda: client.report_inspection(
1256
- args.start_date, args.end_date, args.dimension),
1257
- "report_inspection_history": lambda: client.report_inspection_history(
1258
- args.start_date, args.end_date, args.dimension),
1259
- "report_self_learning": lambda: client.report_self_learning(
1260
- args.begin_ts, args.end_ts, args.time_offset, args.bot_id, args.touch_point),
1261
- "report_feedback": lambda: client.report_feedback(
1262
- args.begin_ts, args.end_ts, args.time_offset, args.bot_id, args.touch_point),
1263
- # Modern /chatReport endpoints (the official Agent Analytics page)
1264
- "chat_report_tickets": lambda: client.chat_report(
1265
- "tickets", args.start_date, args.end_date, args.bot_id,
1266
- args.touch_point or args.channel, args.intent_id, args.agent_id),
1267
- "chat_report_messages": lambda: client.chat_report(
1268
- "messages", args.start_date, args.end_date, args.bot_id,
1269
- args.touch_point or args.channel, args.intent_id, args.agent_id),
1270
- "chat_report_intents": lambda: client.chat_report(
1271
- "intents", args.start_date, args.end_date, args.bot_id,
1272
- args.touch_point or args.channel, args.intent_id, args.agent_id),
1273
- "chat_report_agents": lambda: client.chat_report(
1274
- "agents", args.start_date, args.end_date, args.bot_id,
1275
- args.touch_point or args.channel, args.intent_id, args.agent_id),
1276
- "chat_report_satisfaction": lambda: client.chat_report(
1277
- "satisfaction", args.start_date, args.end_date, args.bot_id,
1278
- args.touch_point or args.channel, args.intent_id, args.agent_id),
1279
- "chat_report_first_response": lambda: client.chat_report(
1280
- "first-response", args.start_date, args.end_date, args.bot_id,
1281
- args.touch_point or args.channel, args.intent_id, args.agent_id),
1282
- "chat_report_return_retention": lambda: client.chat_report(
1283
- "return-retention", args.start_date, args.end_date, args.bot_id,
1284
- args.touch_point or args.channel, args.intent_id, args.agent_id),
1285
- "chat_report_inspection_results": lambda: client.chat_report(
1286
- "inspection-results", args.start_date, args.end_date, args.bot_id,
1287
- args.touch_point or args.channel, args.intent_id, args.agent_id),
1288
- "chat_report_inbox_scripts": lambda: client.chat_report_inbox_scripts(),
1289
- # Transitional /dashboard endpoints (kept for backwards compatibility)
1290
- "dashboard_overview": lambda: client.dashboard(
1291
- "overview", args.start_date, args.end_date, args.bot_id, args.channel,
1292
- args.business_type, args.granularity, args.agent_id),
1293
- "dashboard_message_trend": lambda: client.dashboard(
1294
- "message-trend", args.start_date, args.end_date, args.bot_id, args.channel,
1295
- args.business_type, args.granularity, args.agent_id),
1296
- "dashboard_intent_distribution": lambda: client.dashboard(
1297
- "intent-distribution", args.start_date, args.end_date, args.bot_id, args.channel,
1298
- args.business_type, args.granularity, args.agent_id),
1299
- "dashboard_agent_performance": lambda: client.dashboard(
1300
- "agent-performance", args.start_date, args.end_date, args.bot_id, args.channel,
1301
- args.business_type, args.granularity, args.agent_id),
1302
- "dashboard_intent_trend": lambda: client.dashboard(
1303
- "intent-trend", args.start_date, args.end_date, args.bot_id, args.channel,
1304
- args.business_type, args.granularity, args.agent_id),
1305
- "dashboard_agent_trend": lambda: client.dashboard(
1306
- "agent-trend", args.start_date, args.end_date, args.bot_id, args.channel,
1307
- args.business_type, args.granularity, args.agent_id),
1308
- "dashboard_handoff_heatmap": lambda: client.dashboard(
1309
- "handoff-heatmap", args.start_date, args.end_date, args.bot_id, args.channel,
1310
- args.business_type, args.granularity, args.agent_id),
1311
- "dashboard_turn_funnel": lambda: client.dashboard(
1312
- "turn-funnel", args.start_date, args.end_date, args.bot_id, args.channel,
1313
- args.business_type, args.granularity, args.agent_id),
1314
- "dashboard_handoff_chat_ids": lambda: client.dashboard_handoff_chat_ids(
1315
- args.week_start_date, args.primary_reason, args.secondary_reason, args.limit),
1316
- "report_escalation_drilldown": lambda: client.report_escalation_drilldown(
1317
- args.begin_ts, args.end_ts, args.reason, args.time_offset,
1318
- args.page, args.page_size, args.bot_id, args.touch_point),
1319
- "report_agent_weekly_graph": lambda: client.report_agent_weekly_graph(
1320
- args.begin_ts, args.end_ts, args.time_offset),
1321
- # Voice Agent
1322
- "list_voice_agents": summary_voice_agents,
1323
- "list_voice_transcripts": summary_voice_transcripts,
1324
- "get_voice_transcript": lambda: client.get_voice_transcript(args.history_id),
1325
- }
1326
-
1327
- if cmd not in commands:
1328
- print(f"Unknown command: {args.command}")
1329
- print(f"Available: {', '.join(k.replace('_', '-') for k in commands)}")
1330
- sys.exit(1)
1331
-
1332
- try:
1333
- result = commands[cmd]()
1334
- output(result)
1335
- except Exception as e:
1336
- print(f"Error: {e}", file=sys.stderr)
1337
- sys.exit(1)
1338
-
1339
-
1340
- if __name__ == "__main__":
1341
- main()