@shulex/cxclawctl 0.1.2 → 0.1.4

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/capabilities.lock.json +535 -131
  3. package/dist/cli.js +2203 -127
  4. package/dist/cli.js.map +1 -1
  5. package/package.json +20 -6
  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,219 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Import qa_plan into Solvea knowledge base as QA entries.
4
-
5
- Two input modes:
6
-
7
- 1. --qa-file <path> already-merged qa_plan_<ts>.json
8
- 2. --from-dir <dir> merge all *.json files in <dir> (each is a QA list).
9
- Useful when the agent wrote multiple small batches into
10
- /workspace/tmp/kb-builder/qa_parts/ to avoid the LLM max_tokens
11
- truncation that hits any single huge tool_call. The
12
- merged plan is written to OUTPUTS_DIR/qa_plan_<ts>.json
13
- for traceability before upload.
14
-
15
- QA item format (each list entry):
16
- [
17
- {
18
- "dir_path": ["Product Introduction", "Smart Plugs", "Kasa EP25"],
19
- "question": "Kasa EP25 Smart Plug — What is the Kasa EP25 and what does it do?",
20
- "answer": "The Kasa EP25 is ...\n\n![Product image](https://cdn.example.com/...)"
21
- }
22
- ]
23
-
24
- Usage:
25
- python3 import_qa.py --qa-file /workspace/outputs/kb-builder/qa_plan_<ts>.json
26
- python3 import_qa.py --qa-file /workspace/outputs/kb-builder/qa_plan_<ts>.json --dry-run
27
- python3 import_qa.py --from-dir /workspace/tmp/kb-builder/qa_parts/
28
- python3 import_qa.py --from-dir /workspace/tmp/kb-builder/qa_parts/ --dry-run
29
-
30
- SolveaClient() picks token / project_id / env from environment variables
31
- (SOLVEA_TOKEN / SOLVEA_PROD_TOKEN / SOLVEA_PROJECT_ID / SOLVEA_ENV).
32
- """
33
- from __future__ import annotations
34
- import argparse, json, os, sys
35
- 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"))
36
- from bootstrap import *
37
- from pathlib import Path
38
-
39
- def validate_qa_item(item: object, index: int, max_depth: int = 3) -> tuple[list[str], str, str]:
40
- """Return normalized QA fields or raise a clear schema error."""
41
- if not isinstance(item, dict):
42
- raise ValueError(f"QA item #{index} must be an object, got {type(item).__name__}")
43
-
44
- dir_path = item.get("dir_path")
45
- question = item.get("question")
46
- answer = item.get("answer")
47
-
48
- if not isinstance(dir_path, list) or not dir_path:
49
- raise ValueError(
50
- f"QA item #{index} dir_path must be a non-empty list of folder names, "
51
- f'e.g. ["Product Introduction", "Speakers", "B0117-03018-BK"]; '
52
- f"got {dir_path!r}"
53
- )
54
- if not all(isinstance(name, str) and name.strip() for name in dir_path):
55
- raise ValueError(f"QA item #{index} dir_path entries must be non-empty strings: {dir_path!r}")
56
- if len(dir_path) > max_depth:
57
- raise ValueError(f"QA item #{index} dir_path has {len(dir_path)} levels; maximum is {max_depth}")
58
- if any("/" in name or "\\" in name for name in dir_path):
59
- raise ValueError(
60
- f"QA item #{index} dir_path entries must be folder names, not slash paths: {dir_path!r}"
61
- )
62
- if not isinstance(question, str) or not question.strip():
63
- raise ValueError(f"QA item #{index} question must be a non-empty string")
64
- if not isinstance(answer, str) or not answer.strip():
65
- raise ValueError(f"QA item #{index} answer must be a non-empty markdown string")
66
-
67
- return [name.strip() for name in dir_path], question.strip(), answer.strip()
68
-
69
-
70
- def validate_plan(plan: object, max_depth: int = 3) -> list[tuple[list[str], str, str]]:
71
- if not isinstance(plan, list):
72
- raise ValueError(f"QA plan must be a list, got {type(plan).__name__}")
73
- return [validate_qa_item(item, i + 1, max_depth=max_depth) for i, item in enumerate(plan)]
74
-
75
-
76
- def flatten_folders(nodes: list, result: list | None = None) -> list:
77
- """Recursively flatten a nested folder tree into a flat list."""
78
- if result is None:
79
- result = []
80
- for node in nodes:
81
- result.append(node)
82
- children = node.get("children", [])
83
- if children:
84
- flatten_folders(children, result)
85
- return result
86
-
87
-
88
- def get_or_create_dir(client: SolveaClient, path: list[str], dir_cache: dict) -> str | int:
89
- """Walk dir_path, creating missing folders. Returns final directory ID."""
90
- parent_id = 0
91
- current_path: list[str] = []
92
- for name in path:
93
- current_path.append(name)
94
- key = "/".join(current_path)
95
- if key in dir_cache:
96
- parent_id = dir_cache[key]
97
- continue
98
- # Search in existing folders (API returns {"list": [nested tree]})
99
- folders = client.list_directories()
100
- if isinstance(folders, list):
101
- all_folders = flatten_folders(folders)
102
- else:
103
- raw = folders.get("list") or folders.get("records") or folders.get("data") or []
104
- all_folders = flatten_folders(raw)
105
- match = next(
106
- (f for f in all_folders
107
- if f.get("name") == name and (f.get("parentId") or 0) == parent_id),
108
- None,
109
- )
110
- if match:
111
- fid = match["id"]
112
- else:
113
- result = client.create_folder(name, parent_id=parent_id)
114
- fid = result.get("id") if isinstance(result, dict) else None
115
- if not fid:
116
- print(f" [warn] Could not create folder '{name}' under parentId={parent_id}: {result}", file=sys.stderr)
117
- fid = parent_id # fallback: use parent
118
- dir_cache[key] = fid
119
- parent_id = fid
120
- return parent_id
121
-
122
-
123
- def merge_dir(src_dir: Path) -> tuple[list, Path]:
124
- """Merge every *.json in src_dir (each must be a QA list) into one list,
125
- then write the result to OUTPUTS_DIR/qa_plan_{ts}.json for traceability.
126
-
127
- Files are merged in filename-sorted order so the result is deterministic.
128
- Files whose top-level JSON isn't a list are skipped with a warning.
129
- """
130
- if not src_dir.exists() or not src_dir.is_dir():
131
- raise FileNotFoundError(f"--from-dir not a directory: {src_dir}")
132
-
133
- merged: list = []
134
- parts = sorted(src_dir.glob("*.json"))
135
- if not parts:
136
- raise FileNotFoundError(f"No *.json found under {src_dir}")
137
-
138
- for p in parts:
139
- try:
140
- data = json.loads(p.read_text(encoding="utf-8"))
141
- except Exception as e:
142
- print(f" [warn] skip {p.name}: {e}", file=sys.stderr)
143
- continue
144
- if not isinstance(data, list):
145
- print(f" [warn] skip {p.name}: top-level is not a list", file=sys.stderr)
146
- continue
147
- print(f" [merge] {p.name}: +{len(data)} QA")
148
- merged.extend(data)
149
-
150
- OUTPUTS_DIR.mkdir(parents=True, exist_ok=True)
151
- out = OUTPUTS_DIR / f"qa_plan_{timestamp()}.json"
152
- out.write_text(json.dumps(merged, ensure_ascii=False, indent=2), encoding="utf-8")
153
- print(f"[merge] {len(merged)} QA total -> {out}")
154
- return merged, out
155
-
156
-
157
- def run(qa_file: str | None, from_dir: str | None, dry_run: bool, max_depth: int = 3) -> None:
158
- if from_dir:
159
- plan, qa_file_path = merge_dir(Path(from_dir))
160
- else:
161
- qa_file_path = Path(qa_file)
162
- plan = json.loads(qa_file_path.read_text(encoding="utf-8"))
163
-
164
- normalized_plan = validate_plan(plan, max_depth=max_depth)
165
-
166
- print(f"[import] {len(normalized_plan)} QA entries to import (dry_run={dry_run}) source={qa_file_path}")
167
-
168
- dir_cache: dict[str, str | int] = {}
169
- ok = 0
170
- fail = 0
171
- client = None
172
- if not dry_run:
173
- from client import SolveaClient
174
- client = SolveaClient()
175
-
176
- for i, (dir_path, question, answer) in enumerate(normalized_plan):
177
- if dry_run:
178
- print(f" [{i+1}] DRY dir={dir_path}")
179
- print(f" Q: {question[:80]}")
180
- print(f" A: {answer[:80]}...")
181
- continue
182
-
183
- try:
184
- assert client is not None
185
- dir_id = get_or_create_dir(client, dir_path, dir_cache) if dir_path else ""
186
- client.upload_qa([{
187
- "title": question,
188
- "content": answer,
189
- "directoryId": dir_id,
190
- }])
191
- ok += 1
192
- print(f" [{i+1}/{len(normalized_plan)}] OK {'/'.join(dir_path)} | {question[:60]}")
193
- except Exception as e:
194
- fail += 1
195
- print(f" [{i+1}/{len(normalized_plan)}] FAIL {question[:60]}: {e}")
196
-
197
- if not dry_run:
198
- print(f"\n[import] Done: {ok} OK, {fail} failed")
199
-
200
-
201
- def main():
202
- ap = argparse.ArgumentParser()
203
- src = ap.add_mutually_exclusive_group(required=True)
204
- src.add_argument("--qa-file", dest="qa_file",
205
- help="Path to a pre-merged qa_plan_<ts>.json")
206
- src.add_argument("--from-dir", dest="from_dir",
207
- help="Merge all *.json under this directory (each = QA list); "
208
- "merged result is written to OUTPUTS_DIR/qa_plan_<ts>.json")
209
- ap.add_argument("--dry-run", action="store_true", dest="dry_run")
210
- ap.add_argument("--max-depth", type=int, default=3,
211
- help="Maximum QA directory depth (default: 3; override only when explicitly requested)")
212
- args = ap.parse_args()
213
- if args.max_depth < 1:
214
- ap.error("--max-depth must be positive")
215
- run(args.qa_file, args.from_dir, args.dry_run, args.max_depth)
216
-
217
-
218
- if __name__ == "__main__":
219
- main()
@@ -1,143 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Import URL/Word/PDF sources as DOCUMENT knowledge.
3
-
4
- Input is a JSON list of records:
5
- {"source": "https://...", "dir_path": ["一级", "二级", "三级"]}
6
- {"source": "/mounted/chat-files/manual.docx", "dir_path": ["..."]}
7
-
8
- The default is a dry-run. URL duplicates in the input are reported as
9
- ``skipped_duplicate``; the API response is retained because the backend may
10
- also deduplicate URLs already present in the knowledge base.
11
- """
12
- from __future__ import annotations
13
-
14
- import argparse
15
- import json
16
- import os
17
- import sys
18
- from pathlib import Path
19
-
20
- sys.path.insert(0, os.path.join(os.environ.get("SOLVEA_LIB_DIR", os.path.abspath(
21
- os.path.join(os.path.dirname(__file__), "..", "..", "__lib__"))), "skill-runtime"))
22
- from bootstrap import * # noqa: F403
23
-
24
-
25
- def validate_record(item, index, max_depth=3):
26
- if not isinstance(item, dict) or not isinstance(item.get("source"), str) or not item["source"].strip():
27
- raise ValueError(f"source document #{index} needs a non-empty source")
28
- path = item.get("dir_path", [])
29
- if not isinstance(path, list) or any(not isinstance(x, str) or not x.strip() for x in path):
30
- raise ValueError(f"source document #{index} dir_path must be a list of non-empty names")
31
- if len(path) > max_depth:
32
- raise ValueError(f"source document #{index} dir_path has {len(path)} levels; maximum is {max_depth}")
33
- if any("/" in x or "\\" in x for x in path):
34
- raise ValueError(f"source document #{index} dir_path entries cannot contain slashes")
35
- source = item["source"].strip()
36
- is_url = source.lower().startswith(("http://", "https://"))
37
- if not is_url and not Path(source).is_file():
38
- raise ValueError(f"source document #{index} file not found: {source}")
39
- if not is_url and Path(source).suffix.lower() not in {".doc", ".docx", ".pdf"}:
40
- raise ValueError(f"source document #{index} supports .doc/.docx/.pdf, got {Path(source).suffix}")
41
- return {"source": source, "dir_path": [x.strip() for x in path], "is_url": is_url}
42
-
43
-
44
- def validate_plan(data, max_depth=3):
45
- if not isinstance(data, list):
46
- raise ValueError("source document plan must be a JSON list")
47
- normalized = [validate_record(item, i + 1, max_depth) for i, item in enumerate(data)]
48
- seen = set()
49
- for item in normalized:
50
- if item["is_url"]:
51
- item["status"] = "skipped_duplicate" if item["source"] in seen else "pending"
52
- seen.add(item["source"])
53
- else:
54
- item["status"] = "pending"
55
- return normalized
56
-
57
-
58
- def _flatten(nodes, out=None):
59
- out = [] if out is None else out
60
- for node in nodes:
61
- out.append(node)
62
- _flatten(node.get("children", []), out)
63
- return out
64
-
65
-
66
- def resolve_dir(client, path, cache):
67
- parent = 0
68
- prefix = []
69
- for name in path:
70
- prefix.append(name)
71
- key = "/".join(prefix)
72
- if key in cache:
73
- parent = cache[key]
74
- continue
75
- raw = client.list_directories()
76
- nodes = raw if isinstance(raw, list) else raw.get("list", raw.get("records", raw.get("data", [])))
77
- match = next((x for x in _flatten(nodes) if x.get("name") == name and (x.get("parentId") or 0) == parent), None)
78
- if match:
79
- parent = match["id"]
80
- else:
81
- result = client.create_folder(name, parent_id=parent)
82
- parent = result.get("id") if isinstance(result, dict) else None
83
- if not parent:
84
- raise RuntimeError(f"could not create directory {key!r}: {result}")
85
- cache[key] = parent
86
- return parent
87
-
88
-
89
- def run(plan_file, dry_run=True, max_depth=3):
90
- data = json.loads(Path(plan_file).read_text(encoding="utf-8"))
91
- plan = validate_plan(data, max_depth)
92
- print(f"[source-documents] {len(plan)} records (dry_run={dry_run})")
93
- for item in plan:
94
- print(f" [{item['status']}] {item['source']} -> {'/'.join(item['dir_path']) or '(root)'}")
95
- if dry_run:
96
- return plan
97
-
98
- from client import SolveaClient
99
- client = SolveaClient()
100
- cache = {}
101
- results = []
102
- for item in plan:
103
- if item["status"] == "skipped_duplicate":
104
- results.append({**item, "result": None})
105
- continue
106
- try:
107
- directory_id = resolve_dir(client, item["dir_path"], cache)
108
- if item["is_url"]:
109
- documents = [{"filePath": item["source"], "docType": "TC", "fileType": "website"}]
110
- response = client.create_documents(documents, directory_id=directory_id, task_type="WEB")
111
- else:
112
- key = client.upload_temp_file(item["source"], for_knowledge=True)
113
- # The file-document endpoint derives the loader from the uploaded
114
- # object key; unlike WEBSITE, it does not require fileType.
115
- documents = [{"filePath": key, "docType": "TC"}]
116
- response = client.create_documents(documents, directory_id=directory_id, task_type="FILE")
117
- if not response:
118
- raise RuntimeError("document API returned no created or refreshed record")
119
- results.append({**item, "status": "submitted", "directory_id": directory_id, "result": response})
120
- print(f" [submitted] {item['source']}")
121
- except Exception as exc:
122
- results.append({**item, "status": "failed", "error": str(exc)})
123
- print(f" [failed] {item['source']}: {exc}", file=sys.stderr)
124
- out = OUTPUTS_DIR / f"source_document_import_{timestamp()}.json"
125
- out.parent.mkdir(parents=True, exist_ok=True)
126
- out.write_text(json.dumps(results, ensure_ascii=False, indent=2), encoding="utf-8")
127
- print(f"[source-documents] result={out}")
128
- return results
129
-
130
-
131
- def main():
132
- ap = argparse.ArgumentParser()
133
- ap.add_argument("--plan-file", required=True)
134
- ap.add_argument("--execute", action="store_true", help="Submit documents after explicit user confirmation")
135
- ap.add_argument("--max-depth", type=int, default=3)
136
- args = ap.parse_args()
137
- if args.max_depth < 1:
138
- ap.error("--max-depth must be positive")
139
- run(args.plan_file, dry_run=not args.execute, max_depth=args.max_depth)
140
-
141
-
142
- if __name__ == "__main__":
143
- main()