@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,454 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Export Solvea knowledge base to an Excel file with exact directory paths.
4
-
5
- The script reads the full directory tree once, fetches knowledge through
6
- getKnowledge, joins each knowledge item by directoryId, and writes one Excel
7
- sheet with dynamic Level columns.
8
-
9
- Usage:
10
- python3 /skills/kb-builder/scripts/export_knowledge.py --all
11
- python3 /skills/kb-builder/scripts/export_knowledge.py --directory-id 105809
12
- python3 /skills/kb-builder/scripts/export_knowledge.py --directory-id 105809 --keyword battery
13
- python3 /skills/kb-builder/scripts/export_knowledge.py --directory-id 105809 --no-children
14
- """
15
- from __future__ import annotations
16
-
17
- import argparse
18
- import json
19
- import os
20
- import re
21
- import sys
22
- from dataclasses import dataclass, field
23
- from pathlib import Path
24
- from typing import Any
25
-
26
- sys.path.insert(0, os.path.join(os.environ.get("SOLVEA_LIB_DIR", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "__lib__"))), "skill-runtime"))
27
- from bootstrap import * # noqa: F401,F403
28
-
29
- from openpyxl import Workbook
30
- from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
31
- from openpyxl.utils import get_column_letter
32
-
33
- from client import SolveaClient
34
-
35
-
36
- UNKNOWN_DIRECTORY = "Unknown Directory"
37
- ILLEGAL_EXCEL_CHARS_RE = re.compile(r"[\x00-\x08\x0b\x0c\x0e-\x1f]")
38
-
39
-
40
- @dataclass
41
- class DirectoryIndex:
42
- nodes: dict[int, dict[str, Any]] = field(default_factory=dict)
43
- paths: dict[int, list[str]] = field(default_factory=dict)
44
- children: dict[int, list[int]] = field(default_factory=dict)
45
-
46
- def descendant_ids(self, directory_id: int) -> set[int]:
47
- ids = {int(directory_id)}
48
- for child_id in self.children.get(int(directory_id), []):
49
- ids.update(self.descendant_ids(child_id))
50
- return ids
51
-
52
- def max_depth(self, directory_ids: set[int] | None = None) -> int:
53
- if directory_ids is None:
54
- paths = self.paths.values()
55
- else:
56
- paths = [self.paths.get(int(directory_id), []) for directory_id in directory_ids]
57
- return max((len(path) for path in paths), default=1) or 1
58
-
59
-
60
- def _directory_roots(response: Any) -> list[dict[str, Any]]:
61
- if isinstance(response, list):
62
- return response
63
- if isinstance(response, dict):
64
- value = response.get("list") or response.get("records") or response.get("data") or []
65
- return value if isinstance(value, list) else []
66
- return []
67
-
68
-
69
- def build_directory_index(response: Any) -> DirectoryIndex:
70
- index = DirectoryIndex()
71
-
72
- def walk(nodes: list[dict[str, Any]], parent_path: list[str]) -> None:
73
- for node in nodes:
74
- directory_id = node.get("id")
75
- if directory_id is None:
76
- continue
77
- directory_id = int(directory_id)
78
- name = str(node.get("name") or "").strip()
79
- path = parent_path + ([name] if name else [])
80
- parent_id = int(node.get("parentId") or 0)
81
-
82
- index.nodes[directory_id] = node
83
- index.paths[directory_id] = path
84
- index.children.setdefault(parent_id, []).append(directory_id)
85
- index.children.setdefault(directory_id, [])
86
-
87
- children = node.get("children") or []
88
- if isinstance(children, list) and children:
89
- walk(children, path)
90
-
91
- walk(_directory_roots(response), [])
92
- return index
93
-
94
-
95
- def _knowledge_items(response: Any) -> list[dict[str, Any]]:
96
- if isinstance(response, list):
97
- return response
98
- if isinstance(response, dict):
99
- value = response.get("list") or response.get("records") or response.get("data") or []
100
- return value if isinstance(value, list) else []
101
- return []
102
-
103
-
104
- def _knowledge_total(response: Any, fallback: int) -> int:
105
- if isinstance(response, dict):
106
- for key in ("total", "totalCount", "count"):
107
- value = response.get(key)
108
- if isinstance(value, int):
109
- return value
110
- return fallback
111
-
112
-
113
- def fetch_knowledge_page(
114
- client: SolveaClient,
115
- *,
116
- page_index: int,
117
- page_size: int,
118
- keyword: str,
119
- knowledge_type: str,
120
- directory_id: int | None,
121
- ) -> Any:
122
- return client.list_knowledge(
123
- page_index=page_index,
124
- page_size=page_size,
125
- keyword=keyword,
126
- knowledge_type=knowledge_type,
127
- directory_id=directory_id,
128
- )
129
-
130
-
131
- def fetch_all_knowledge(
132
- client: SolveaClient,
133
- *,
134
- directory_id: int | None,
135
- keyword: str,
136
- knowledge_type: str,
137
- page_size: int,
138
- ) -> list[dict[str, Any]]:
139
- items: list[dict[str, Any]] = []
140
- page = 1
141
- while True:
142
- response = fetch_knowledge_page(
143
- client,
144
- page_index=page,
145
- page_size=page_size,
146
- keyword=keyword,
147
- knowledge_type=knowledge_type,
148
- directory_id=directory_id,
149
- )
150
- page_items = _knowledge_items(response)
151
- if not page_items:
152
- break
153
- items.extend(page_items)
154
-
155
- total = _knowledge_total(response, len(items))
156
- if len(items) >= total or len(page_items) < page_size:
157
- break
158
- page += 1
159
- return items
160
-
161
-
162
- def selected_directory_ids(
163
- directory_index: DirectoryIndex,
164
- directory_ids: list[int] | None,
165
- *,
166
- include_children: bool,
167
- ) -> set[int] | None:
168
- if not directory_ids:
169
- return None
170
- selected: set[int] = set()
171
- for directory_id in directory_ids:
172
- directory_id = int(directory_id)
173
- if include_children:
174
- selected.update(directory_index.descendant_ids(directory_id))
175
- else:
176
- selected.add(directory_id)
177
- return selected
178
-
179
-
180
- def normalize_item(item: dict[str, Any]) -> dict[str, Any]:
181
- return {
182
- "id": item.get("id", item.get("knowledgeId", item.get("kuId", ""))),
183
- "directoryId": item.get("directoryId"),
184
- "question": item.get("question", item.get("title", item.get("name", ""))) or "",
185
- "answer": item.get("answer", item.get("content", item.get("brief", ""))) or "",
186
- "type": item.get("type", ""),
187
- "status": item.get("status", item.get("knowledgeStatus", "")) or "",
188
- }
189
-
190
-
191
- def clean_excel_value(value: Any) -> Any:
192
- if not isinstance(value, str):
193
- return value
194
- return ILLEGAL_EXCEL_CHARS_RE.sub("", value)
195
-
196
-
197
- def collect_knowledge(
198
- client: SolveaClient,
199
- directory_index: DirectoryIndex,
200
- *,
201
- directory_ids: list[int] | None,
202
- include_children: bool,
203
- keyword: str,
204
- knowledge_type: str,
205
- page_size: int,
206
- ) -> tuple[list[dict[str, Any]], set[int] | None]:
207
- selected_ids = selected_directory_ids(
208
- directory_index,
209
- directory_ids,
210
- include_children=include_children,
211
- )
212
- raw_items: list[dict[str, Any]] = []
213
-
214
- if selected_ids is None:
215
- raw_items = fetch_all_knowledge(
216
- client,
217
- directory_id=None,
218
- keyword=keyword,
219
- knowledge_type=knowledge_type,
220
- page_size=page_size,
221
- )
222
- else:
223
- for directory_id in sorted(selected_ids):
224
- raw_items.extend(
225
- fetch_all_knowledge(
226
- client,
227
- directory_id=directory_id,
228
- keyword=keyword,
229
- knowledge_type=knowledge_type,
230
- page_size=page_size,
231
- )
232
- )
233
-
234
- deduped: dict[str, dict[str, Any]] = {}
235
- for item in raw_items:
236
- normalized = normalize_item(item)
237
- key = str(normalized["id"] or f"{normalized['directoryId']}:{normalized['question']}")
238
- deduped[key] = normalized
239
- return list(deduped.values()), selected_ids
240
-
241
-
242
- def _path_for_item(directory_index: DirectoryIndex, item: dict[str, Any]) -> list[str]:
243
- directory_id = item.get("directoryId")
244
- if directory_id is None:
245
- return [UNKNOWN_DIRECTORY]
246
- try:
247
- return directory_index.paths.get(int(directory_id), [UNKNOWN_DIRECTORY])
248
- except (TypeError, ValueError):
249
- return [UNKNOWN_DIRECTORY]
250
-
251
-
252
- def write_excel(
253
- items: list[dict[str, Any]],
254
- directory_index: DirectoryIndex,
255
- output_path: Path,
256
- *,
257
- selected_ids: set[int] | None,
258
- ) -> None:
259
- paths = [_path_for_item(directory_index, item) for item in items]
260
- if selected_ids is None:
261
- max_depth = max((len(path) for path in paths), default=directory_index.max_depth())
262
- else:
263
- max_depth = max(
264
- directory_index.max_depth(selected_ids),
265
- max((len(path) for path in paths), default=1),
266
- )
267
- max_depth = max(max_depth, 1)
268
-
269
- wb = Workbook()
270
- ws = wb.active
271
- ws.title = "Knowledge Base"
272
-
273
- headers = [f"Level {i}" for i in range(1, max_depth + 1)]
274
- headers.extend(["Question", "Answer", "KU ID", "Type", "Status", "Directory ID"])
275
-
276
- header_fill = PatternFill("solid", fgColor="1F4E79")
277
- header_font = Font(bold=True, color="FFFFFF")
278
- thin = Side(style="thin", color="BFBFBF")
279
- border = Border(left=thin, right=thin, top=thin, bottom=thin)
280
-
281
- for col, header in enumerate(headers, 1):
282
- cell = ws.cell(row=1, column=col, value=header)
283
- cell.fill = header_fill
284
- cell.font = header_font
285
- cell.alignment = Alignment(horizontal="center", vertical="center")
286
- cell.border = border
287
-
288
- rows = sorted(
289
- zip(items, paths),
290
- key=lambda pair: (pair[1], str(pair[0].get("question", "")), str(pair[0].get("id", ""))),
291
- )
292
- for row_idx, (item, path) in enumerate(rows, 2):
293
- padded_path = path + [""] * (max_depth - len(path))
294
- values = padded_path[:max_depth] + [
295
- item.get("question", ""),
296
- item.get("answer", ""),
297
- item.get("id", ""),
298
- item.get("type", ""),
299
- item.get("status", ""),
300
- item.get("directoryId", ""),
301
- ]
302
- for col, value in enumerate(values, 1):
303
- cell = ws.cell(row=row_idx, column=col, value=clean_excel_value(value))
304
- cell.border = border
305
- cell.alignment = Alignment(vertical="top", wrap_text=col > max_depth)
306
-
307
- widths = [22] * max_depth + [48, 80, 14, 14, 18, 14]
308
- for col, width in enumerate(widths, 1):
309
- ws.column_dimensions[get_column_letter(col)].width = width
310
- ws.freeze_panes = ws.cell(row=2, column=max_depth + 1).coordinate
311
- ws.auto_filter.ref = ws.dimensions
312
-
313
- output_path.parent.mkdir(parents=True, exist_ok=True)
314
- wb.save(output_path)
315
-
316
-
317
- def write_cache(
318
- directory_response: Any,
319
- items: list[dict[str, Any]],
320
- *,
321
- directory_ids: list[int] | None,
322
- include_children: bool,
323
- keyword: str,
324
- knowledge_type: str,
325
- ) -> None:
326
- export_tmp = TMP_DIR / "export"
327
- export_tmp.mkdir(parents=True, exist_ok=True)
328
- (export_tmp / "directories.json").write_text(
329
- json.dumps(directory_response, ensure_ascii=False, indent=2),
330
- encoding="utf-8",
331
- )
332
- (export_tmp / "knowledge_items.json").write_text(
333
- json.dumps(items, ensure_ascii=False, indent=2),
334
- encoding="utf-8",
335
- )
336
- (export_tmp / "request.json").write_text(
337
- json.dumps(
338
- {
339
- "directoryIds": directory_ids,
340
- "includeChildren": include_children,
341
- "keyword": keyword,
342
- "type": knowledge_type,
343
- },
344
- ensure_ascii=False,
345
- indent=2,
346
- ),
347
- encoding="utf-8",
348
- )
349
-
350
-
351
- def build_directory_stats(items: list[dict[str, Any]], directory_index: DirectoryIndex) -> list[dict[str, Any]]:
352
- counts: dict[str, dict[str, Any]] = {}
353
- for item in items:
354
- directory_id = item.get("directoryId")
355
- path = _path_for_item(directory_index, item)
356
- path_text = " / ".join(path)
357
- entry = counts.setdefault(
358
- path_text,
359
- {"path": path_text, "directoryId": directory_id, "count": 0},
360
- )
361
- entry["count"] += 1
362
- return sorted(counts.values(), key=lambda row: (-row["count"], row["path"]))
363
-
364
-
365
- def export_knowledge(
366
- client: SolveaClient,
367
- *,
368
- output_path: Path | None = None,
369
- directory_ids: list[int] | None = None,
370
- include_children: bool = True,
371
- keyword: str = "",
372
- knowledge_type: str = "ALL",
373
- page_size: int = 1000,
374
- write_tmp_cache: bool = False,
375
- ) -> dict[str, Any]:
376
- directory_response = client.list_directories()
377
- directory_index = build_directory_index(directory_response)
378
- items, selected_ids = collect_knowledge(
379
- client,
380
- directory_index,
381
- directory_ids=directory_ids,
382
- include_children=include_children,
383
- keyword=keyword,
384
- knowledge_type=knowledge_type,
385
- page_size=page_size,
386
- )
387
-
388
- if output_path is None:
389
- output_path = OUTPUTS_DIR / f"kb_export_{timestamp()}.xlsx"
390
- else:
391
- output_path = Path(output_path)
392
-
393
- write_excel(items, directory_index, output_path, selected_ids=selected_ids)
394
- directory_stats = build_directory_stats(items, directory_index)
395
- if write_tmp_cache:
396
- write_cache(
397
- directory_response,
398
- items,
399
- directory_ids=directory_ids,
400
- include_children=include_children,
401
- keyword=keyword,
402
- knowledge_type=knowledge_type,
403
- )
404
-
405
- return {
406
- "output_path": str(output_path),
407
- "knowledge_count": len(items),
408
- "directory_count": len(directory_index.nodes),
409
- "selected_directory_count": len(selected_ids) if selected_ids is not None else None,
410
- "directory_stats": directory_stats,
411
- }
412
-
413
-
414
- def main() -> None:
415
- parser = argparse.ArgumentParser(description="Export Solvea knowledge with directory columns")
416
- parser.add_argument("--all", action="store_true", dest="export_all",
417
- help="Export all knowledge. This is the default when no --directory-id is provided.")
418
- parser.add_argument("--directory-id", action="append", type=int, default=[],
419
- help="Directory ID to export. Can be specified multiple times.")
420
- parser.add_argument("--no-children", action="store_true",
421
- help="Only export the specified directory IDs, not their descendants.")
422
- parser.add_argument("--keyword", default="", help="Keyword filter passed to getKnowledge")
423
- parser.add_argument("--type", dest="knowledge_type", default="ALL",
424
- help="Knowledge type: ALL / QA / FILE / WEBSITE / PRODUCT / LEARN_SELF")
425
- parser.add_argument("--page-size", type=int, default=1000)
426
- parser.add_argument("--output", default="", help="Optional output xlsx path")
427
- args = parser.parse_args()
428
-
429
- output_path = Path(args.output) if args.output else None
430
- directory_ids = args.directory_id or None
431
- result = export_knowledge(
432
- SolveaClient(),
433
- output_path=output_path,
434
- directory_ids=directory_ids,
435
- include_children=not args.no_children,
436
- keyword=args.keyword,
437
- knowledge_type=args.knowledge_type,
438
- page_size=args.page_size,
439
- write_tmp_cache=True,
440
- )
441
-
442
- print(f"[export] directories={result['directory_count']}")
443
- if result["selected_directory_count"] is not None:
444
- print(f"[export] selected_directories={result['selected_directory_count']}")
445
- print(f"[export] knowledge={result['knowledge_count']}")
446
- for row in result["directory_stats"][:20]:
447
- print(f"[export] directory count={row['count']} id={row['directoryId']} path={row['path']}")
448
- if len(result["directory_stats"]) > 20:
449
- print(f"[export] directory ... {len(result['directory_stats']) - 20} more")
450
- print(f"[export] output={result['output_path']}")
451
-
452
-
453
- if __name__ == "__main__":
454
- main()