@shulex/cxclawctl 0.1.5 → 0.1.6

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.
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { readFile as readFile5 } from "fs/promises";
4
+ import { readFile as readFile6 } from "fs/promises";
5
5
 
6
6
  // src/auth.ts
7
7
  import open from "open";
@@ -9,7 +9,7 @@ import open from "open";
9
9
  // package.json
10
10
  var package_default = {
11
11
  name: "@shulex/cxclawctl",
12
- version: "0.1.5",
12
+ version: "0.1.6",
13
13
  description: "Solvea CXClaw CLI and Codex plugin runtime",
14
14
  type: "module",
15
15
  bin: {
@@ -38,6 +38,7 @@ var package_default = {
38
38
  },
39
39
  dependencies: {
40
40
  "@alicloud/log": "^1.2.6",
41
+ "@aws-sdk/client-s3": "^3.1156.0",
41
42
  "@napi-rs/keyring": "^2.1.2",
42
43
  "compare-versions": "^6.1.1",
43
44
  exceljs: "^4.4.0",
@@ -189,13 +190,32 @@ function positionals(args) {
189
190
  }
190
191
 
191
192
  // src/business.ts
192
- import { dirname, resolve } from "path";
193
+ import { dirname as dirname2, resolve } from "path";
193
194
  import { fileURLToPath } from "url";
194
195
 
195
196
  // src/solvea/commands.ts
196
- import { readFile as readFile2 } from "fs/promises";
197
+ import { readFile as readFile3 } from "fs/promises";
197
198
 
198
199
  // src/solvea/client.ts
200
+ import { mkdir, readFile, writeFile } from "fs/promises";
201
+ import { basename, dirname, extname } from "path";
202
+ import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
203
+ var KNOWLEDGE_TYPES = /* @__PURE__ */ new Set(["QA", "LEARN_SELF", "FILE", "WEBSITE", "PRODUCT"]);
204
+ var asObject = (value, message) => {
205
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(message);
206
+ return value;
207
+ };
208
+ var asPositiveInt = (value, message) => {
209
+ const number = Number(value);
210
+ if (!Number.isSafeInteger(number) || number <= 0) throw new Error(message);
211
+ return number;
212
+ };
213
+ var knowledgeType = (value) => {
214
+ const normalized = String(value ?? "").toUpperCase();
215
+ if (!KNOWLEDGE_TYPES.has(normalized)) throw new Error("knowledge type must be QA, LEARN_SELF, FILE, WEBSITE, or PRODUCT");
216
+ return normalized;
217
+ };
218
+ var isolationKnowledgeType = (value) => ({ QA: "QA", LEARN_SELF: "QA", FILE: "DOCUMENT", WEBSITE: "DOCUMENT", PRODUCT: "PRODUCT" })[knowledgeType(value)];
199
219
  var SolveaClient = class {
200
220
  constructor(session) {
201
221
  this.session = session;
@@ -304,14 +324,25 @@ var SolveaClient = class {
304
324
  return this.post("/api_v2/gpt/ap/createAgent", { projectId: Number(await this.projectId()), ...data3 });
305
325
  }
306
326
  async updateAgent(data3) {
307
- return this.post("/api_v2/gpt/ap/updateAgent", data3);
327
+ const payload = { ...data3, projectId: Number(await this.projectId()) };
328
+ if (payload.agentId === void 0 && payload.id !== void 0) payload.agentId = payload.id;
329
+ return this.post("/api_v2/gpt/ap/updateAgent", payload);
308
330
  }
309
331
  async updateChatflow(agentId, graph, publish = false) {
310
332
  const current = await this.getAgent(agentId);
311
333
  const agent = Array.isArray(current) ? current[0] : current;
312
334
  if (!agent || agent.type !== "CHAT_FLOW") throw new Error(`Agent ${agentId} is not CHAT_FLOW`);
335
+ if (!graph || !Array.isArray(graph.nodes) || !Array.isArray(graph.edges)) throw new Error("chatFlowGraph must be an object with nodes and edges");
313
336
  const payload = { ...agent, agentId: agent.id ?? Number(agentId), meta: { ...agent.meta ?? {}, chatFlowGraph: graph } };
314
- return this.updateAgent({ ...payload, ...publish ? { publish: true } : {} });
337
+ if (publish) {
338
+ delete payload.backupType;
339
+ delete payload.meta.backupType;
340
+ delete payload.meta.draft_flow_updatetime;
341
+ } else {
342
+ payload.backupType = "DRAFT";
343
+ payload.meta.draft_flow_updatetime = Date.now();
344
+ }
345
+ return this.updateAgent(payload);
315
346
  }
316
347
  async listPlugins() {
317
348
  return this.post("/api_v2/gpt/ap/queryPlugin", { projectId: Number(await this.projectId()), needRelation: false });
@@ -323,7 +354,9 @@ var SolveaClient = class {
323
354
  return this.post("/api_v2/gpt/ap/createPlugin", { projectId: Number(await this.projectId()), ...data3 });
324
355
  }
325
356
  async updatePlugin(data3) {
326
- return this.post("/api_v2/gpt/ap/updatePlugin", { projectId: Number(await this.projectId()), ...data3 });
357
+ const payload = { projectId: Number(await this.projectId()), ...data3 };
358
+ if (payload.pluginId === void 0 && payload.id !== void 0) payload.pluginId = payload.id;
359
+ return this.post("/api_v2/gpt/ap/updatePlugin", payload);
327
360
  }
328
361
  async deletePlugin(id) {
329
362
  return this.post("/api_v2/gpt/ap/deletePlugin", { projectId: Number(await this.projectId()), pluginId: Number(id) });
@@ -375,11 +408,48 @@ var SolveaClient = class {
375
408
  }
376
409
  async chat(data3) {
377
410
  const bot = String(data3.botId ?? await this.botId());
378
- return this.post(`/api_v2/gpt/bot/${bot}/chat`, { botId: Number(bot), projectId: Number(await this.projectId()), option: { debug: true }, ...data3 });
411
+ return this.post(`/api_v2/gpt/bot/${bot}/chat`, { ...data3, botId: Number(bot), projectId: Number(await this.projectId()), option: data3.option ?? { debug: true } });
412
+ }
413
+ async chatStream(data3) {
414
+ const bot = String(data3.botId ?? await this.botId());
415
+ const body = { ...data3, botId: Number(bot), projectId: Number(await this.projectId()), option: data3.option ?? { debug: true } };
416
+ const cookie = this.env === "prod" ? "_token" : "_token_staging";
417
+ const response = await fetch(`${this.baseUrl}/api_v2/gpt/bot/${encodeURIComponent(bot)}/chat/stream`, {
418
+ method: "POST",
419
+ headers: {
420
+ accept: "text/event-stream",
421
+ "content-type": "application/json",
422
+ origin: this.baseUrl,
423
+ referer: `${this.baseUrl}/chatbot`,
424
+ cookie: `${cookie}=${this.session.tenant.businessToken}`,
425
+ "x-cxclaw-client-version": process.env.CXCLAW_PLUGIN_VERSION ?? "unknown"
426
+ },
427
+ body: JSON.stringify(body),
428
+ signal: AbortSignal.timeout(3e4)
429
+ });
430
+ const text = await response.text();
431
+ if (!response.ok) throw new Error(`HTTP ${response.status} POST /api_v2/gpt/bot/${bot}/chat/stream: ${text}`);
432
+ let finalAction;
433
+ const content = [];
434
+ for (const line of text.split(/\r?\n/)) {
435
+ if (!line.startsWith("data:")) continue;
436
+ const raw = line.slice(5).trim();
437
+ if (!raw) continue;
438
+ let event;
439
+ try {
440
+ event = asObject(JSON.parse(raw), "stream event must be an object");
441
+ } catch {
442
+ continue;
443
+ }
444
+ if (event.errno !== void 0 && ![0, "0"].includes(event.errno)) throw new Error(`API error POST /api_v2/gpt/bot/${bot}/chat/stream: ${String(event.errmsg ?? event.message ?? event.errno)}`);
445
+ if (event.content) content.push(String(event.content));
446
+ if (event.isEnd) finalAction = event.data && typeof event.data === "object" && !Array.isArray(event.data) ? event.data : event;
447
+ }
448
+ return finalAction ?? { content: content.join(""), isEnd: true };
379
449
  }
380
450
  async chatAsync(data3) {
381
451
  const bot = String(data3.botId ?? await this.botId());
382
- return this.post(`/api_v2/gpt/bot/${bot}/chat/async`, { botId: Number(bot), projectId: Number(await this.projectId()), option: { debug: true }, ...data3 });
452
+ return this.post(`/api_v2/gpt/bot/${bot}/chat/async`, { ...data3, botId: Number(bot), projectId: Number(await this.projectId()), option: data3.option ?? { debug: true } });
383
453
  }
384
454
  async asyncResult(receipt, botId) {
385
455
  const bot = botId ?? await this.botId();
@@ -410,10 +480,10 @@ var SolveaClient = class {
410
480
  return this.post(`/api_v2/gpt/project/${await this.projectId()}/strategy?v4=true`, data3);
411
481
  }
412
482
  async updateStrategy(id, data3) {
413
- return this.request("PUT", `/api_v2/gpt/project/${await this.projectId()}/strategy/${encodeURIComponent(id)}`, data3);
483
+ return this.request("PUT", `/api_v2/gpt/project/${await this.projectId()}/strategy/${encodeURIComponent(id)}`, data3, { v4: "true" });
414
484
  }
415
485
  async toggleStrategy(id, enabled2) {
416
- return this.request("PUT", `/api_v2/gpt/project/${await this.projectId()}/strategy/${encodeURIComponent(id)}/status`, { status: enabled2 ? "ENABLED" : "DISABLED" });
486
+ return this.request("PUT", `/api_v2/gpt/project/${await this.projectId()}/strategy/${encodeURIComponent(id)}`, { status: enabled2 ? "ENABLED" : "DISABLED" }, { v4: "true" });
417
487
  }
418
488
  async qualityStatus() {
419
489
  return this.get("/api_v2/intelli/inspection/ticket/status");
@@ -494,11 +564,212 @@ var SolveaClient = class {
494
564
  async getKnowledge(id) {
495
565
  return this.get(`${this.qamPrefix}/project/${await this.projectId()}/ku/${encodeURIComponent(id)}`);
496
566
  }
567
+ async getDocument(id) {
568
+ return this.get(`${this.qamPrefix}/project/${await this.projectId()}/document/${encodeURIComponent(id)}`);
569
+ }
570
+ async getProductKnowledge(id) {
571
+ return this.get(`${this.qamPrefix}/project/${await this.projectId()}/ku_product/${encodeURIComponent(id)}`);
572
+ }
573
+ async getKnowledgeByType(id, type) {
574
+ const normalized = knowledgeType(type);
575
+ if (normalized === "FILE" || normalized === "WEBSITE") return this.getDocument(id);
576
+ if (normalized === "PRODUCT") return this.getProductKnowledge(id);
577
+ return this.getKnowledge(id);
578
+ }
497
579
  async updateKnowledge(id, data3) {
498
- return this.request("PUT", `${this.qamPrefix}/project/${await this.projectId()}/ku`, { ...data3, kuId: Number(id) });
580
+ const payload = { ...data3 };
581
+ if (payload.directoryId === void 0) {
582
+ const current = asObject(await this.getKnowledge(id), `could not read knowledge ${id} before update`);
583
+ if (current.directoryId !== void 0) payload.directoryId = current.directoryId;
584
+ }
585
+ return this.request("PUT", `${this.qamPrefix}/project/${await this.projectId()}/ku`, { ...payload, kuId: asPositiveInt(id, "knowledge id must be a positive integer") });
499
586
  }
500
587
  async uploadQa(items) {
501
- return this.post(`${this.qamPrefix}/project/${await this.projectId()}/kus`, items);
588
+ if (!Array.isArray(items) || !items.length) throw new Error("QA items must be a non-empty array");
589
+ const payload = items.map((value, index) => {
590
+ const item = asObject(value, `QA item #${index + 1} must be an object`);
591
+ const title = String(item.title ?? "").trim();
592
+ const content = String(item.content ?? "").trim();
593
+ if (!title) throw new Error(`QA item #${index + 1} requires title`);
594
+ if (!content) throw new Error(`QA item #${index + 1} requires content`);
595
+ return {
596
+ title,
597
+ content,
598
+ forceIndex: item.forceIndex ?? true,
599
+ type: "QA",
600
+ published: item.published ?? true,
601
+ taskType: item.taskType ?? "MANUAL",
602
+ metadata: item.metadata ?? { tags: [] },
603
+ directoryId: item.directoryId ?? "",
604
+ isVisible: item.isVisible ?? true
605
+ };
606
+ });
607
+ return this.post(`${this.qamPrefix}/project/${await this.projectId()}/kus`, payload);
608
+ }
609
+ async createDocuments(documents, directoryId, taskType) {
610
+ if (!documents.length) throw new Error("documents must be a non-empty array");
611
+ return this.post(`${this.qamPrefix}/project/${await this.projectId()}/documents`, {
612
+ documents,
613
+ published: true,
614
+ taskType,
615
+ taskUrl: "MANUAL",
616
+ isFullSiteCrawl: false,
617
+ metadata: { tags: [] },
618
+ directoryId,
619
+ isVisible: true
620
+ });
621
+ }
622
+ async uploadTempFile(filePath, forKnowledge = false) {
623
+ const credentialPath = forKnowledge ? `${this.qamPrefix}/project/${await this.projectId()}/sts_credentials` : `/api_v2/gpt/project/${await this.projectId()}/sts_credentials`;
624
+ const credentials = asObject(
625
+ await this.get(credentialPath),
626
+ "STS credentials must be an object"
627
+ );
628
+ const required = ["accessKeyId", "secretAccessKey", "sessionToken", "region", "bucketName", "path"];
629
+ const missing = required.filter((key2) => !credentials[key2]);
630
+ if (missing.length) throw new Error(`knowledge STS credentials missing fields: ${missing.join(", ")}`);
631
+ const key = `${String(credentials.path)}${basename(filePath)}`;
632
+ const client = new S3Client({
633
+ region: String(credentials.region),
634
+ credentials: {
635
+ accessKeyId: String(credentials.accessKeyId),
636
+ secretAccessKey: String(credentials.secretAccessKey),
637
+ sessionToken: String(credentials.sessionToken)
638
+ }
639
+ });
640
+ const contentTypes = {
641
+ ".doc": "application/msword",
642
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
643
+ ".pdf": "application/pdf"
644
+ };
645
+ await client.send(new PutObjectCommand({
646
+ Bucket: String(credentials.bucketName),
647
+ Key: key,
648
+ Body: await readFile(filePath),
649
+ ContentType: contentTypes[extname(filePath).toLowerCase()] ?? "application/octet-stream"
650
+ }));
651
+ return key;
652
+ }
653
+ async uploadKnowledgeFile(filePath) {
654
+ return this.uploadTempFile(filePath, true);
655
+ }
656
+ async downloadFile(path, target, query2) {
657
+ const url = new URL(`${this.baseUrl}${path}`);
658
+ for (const [key, value] of Object.entries(query2 ?? {})) url.searchParams.set(key, String(value));
659
+ const cookie = this.env === "prod" ? "_token" : "_token_staging";
660
+ const response = await fetch(url, {
661
+ headers: { accept: "*/*", origin: this.baseUrl, referer: `${this.baseUrl}/chatbot`, cookie: `${cookie}=${this.session.tenant.businessToken}` },
662
+ signal: AbortSignal.timeout(3e4)
663
+ });
664
+ if (!response.ok) throw new Error(`HTTP ${response.status} GET ${path}: ${await response.text()}`);
665
+ await mkdir(dirname(target), { recursive: true });
666
+ await writeFile(target, new Uint8Array(await response.arrayBuffer()));
667
+ return target;
668
+ }
669
+ async downloadLabelTemplate(templateType, target) {
670
+ if (!["VOC_LABEL", "VOC_LABEL_INIT"].includes(templateType)) throw new Error("template type must be VOC_LABEL or VOC_LABEL_INIT");
671
+ return this.downloadFile(`/api_v2/gpt/label/project/${await this.projectId()}/downloadLabelTemplate`, target, { type: templateType });
672
+ }
673
+ async downloadLabels(treeId, target) {
674
+ return this.downloadFile(`/api_v2/gpt/label/project/${await this.projectId()}/downloadLabel`, target, { treeId: asPositiveInt(treeId, "tree id must be a positive integer") });
675
+ }
676
+ async uploadLabelFile(treeId, filePath, init = false) {
677
+ const fileKey = await this.uploadTempFile(filePath);
678
+ const result = await this.post(`/api_v2/gpt/label/project/${await this.projectId()}/uploadLabel`, {
679
+ treeId: asPositiveInt(treeId, "tree id must be a positive integer"),
680
+ file: fileKey,
681
+ ...init ? { uploadWay: "INIT" } : {}
682
+ });
683
+ return { fileKey, result };
684
+ }
685
+ async updateKus(value) {
686
+ const source = Array.isArray(value) ? value : asObject(value, "batch knowledge update must be an array or object with list").list;
687
+ if (!Array.isArray(source) || !source.length) throw new Error("batch knowledge update requires a non-empty array");
688
+ const list = [];
689
+ for (const [index, value2] of source.entries()) {
690
+ const row = { ...asObject(value2, `batch knowledge item #${index + 1} must be an object`) };
691
+ row.kuId = asPositiveInt(row.kuId ?? row.id, `batch knowledge item #${index + 1} requires kuId or id`);
692
+ delete row.id;
693
+ if (row.directoryId === void 0) {
694
+ const current = asObject(await this.getKnowledge(String(row.kuId)), `could not read knowledge ${row.kuId} before update`);
695
+ if (current.directoryId !== void 0) row.directoryId = current.directoryId;
696
+ }
697
+ list.push(row);
698
+ }
699
+ return this.request("PUT", `${this.qamPrefix}/project/${await this.projectId()}/kus`, { list });
700
+ }
701
+ async updateDocumentContent(id, type, changes) {
702
+ const normalized = knowledgeType(type);
703
+ if (normalized !== "FILE" && normalized !== "WEBSITE") throw new Error("document type must be FILE or WEBSITE");
704
+ const unknown = Object.keys(changes).filter((key) => !["fileText", "filePath", "title"].includes(key));
705
+ if (unknown.length) throw new Error(`unsupported document content fields: ${unknown.sort().join(", ")}`);
706
+ if (changes.fileText === void 0 && changes.filePath === void 0) throw new Error("document update requires fileText or filePath");
707
+ const documentId = asPositiveInt(id, "document id must be a positive integer");
708
+ const current = asObject(await this.getDocument(id), `could not read document ${id} before update`);
709
+ const payload = {
710
+ type: normalized,
711
+ directoryId: current.directoryId,
712
+ fileName: current.fileName ?? current.name ?? current.title,
713
+ title: current.title ?? current.fileName ?? current.name,
714
+ metadata: current.metadata && typeof current.metadata === "object" && !Array.isArray(current.metadata) ? current.metadata : {},
715
+ ...changes,
716
+ knowledgeIds: [documentId]
717
+ };
718
+ return this.request("PUT", `${this.qamPrefix}/project/${await this.projectId()}/document/${documentId}/contentV2`, payload);
719
+ }
720
+ async createProductKnowledge(data3) {
721
+ if (!data3.title || !data3.spuUrl) throw new Error("product knowledge requires title and spuUrl");
722
+ return this.post(`${this.qamPrefix}/project/${await this.projectId()}/ku_product`, {
723
+ source: "MANUAL",
724
+ published: true,
725
+ isVisible: true,
726
+ metadata: { tags: [] },
727
+ ...data3
728
+ });
729
+ }
730
+ async updateProductKnowledge(id, changes) {
731
+ const allowed = /* @__PURE__ */ new Set(["productId", "tags", "documentId", "title", "handle", "vendor", "category", "brand", "productStatus", "country", "spuUrl", "image", "platform", "currency", "price", "compareAtPrice", "describe", "source", "skuList", "directoryId", "published", "metadata", "specialTagMap", "isVisible", "optionName"]);
732
+ const unknown = Object.keys(changes).filter((key) => !allowed.has(key) && key !== "id" && key !== "kuProductId");
733
+ if (unknown.length) throw new Error(`unsupported product fields: ${unknown.sort().join(", ")}`);
734
+ const current = asObject(await this.getProductKnowledge(id), `could not read product knowledge ${id} before update`);
735
+ const payload = {};
736
+ for (const key of allowed) if (current[key] !== void 0) payload[key] = current[key];
737
+ for (const key of allowed) if (changes[key] !== void 0) payload[key] = changes[key];
738
+ const missing = ["title", "describe", "spuUrl", "image", "source", "published", "isVisible"].filter((key) => payload[key] === void 0 || payload[key] === "");
739
+ if (missing.length) throw new Error(`product detail is incomplete: ${missing.join(", ")}`);
740
+ return this.request("PUT", `${this.qamPrefix}/project/${await this.projectId()}/ku_product`, { ...payload, kuProductId: asPositiveInt(id, "product id must be a positive integer") });
741
+ }
742
+ async updateKuProducts(value) {
743
+ const source = Array.isArray(value) ? value : asObject(value, "batch product update must be an array or object with list").list;
744
+ if (!Array.isArray(source) || !source.length) throw new Error("batch product update requires a non-empty array");
745
+ const list = source.map((value2, index) => {
746
+ const row = asObject(value2, `batch product item #${index + 1} must be an object`);
747
+ const entry2 = { id: asPositiveInt(row.id ?? row.kuProductId, `batch product item #${index + 1} requires id or kuProductId`) };
748
+ if (row.mainPush !== void 0) entry2.mainPush = Boolean(row.mainPush);
749
+ if (row.enable !== void 0) entry2.enable = Boolean(row.enable);
750
+ else if (row.published !== void 0) entry2.enable = Boolean(row.published);
751
+ return entry2;
752
+ });
753
+ return this.request("PUT", `${this.qamPrefix}/project/${await this.projectId()}/ku_products`, { list });
754
+ }
755
+ async operateKnowledge(value, confirmDelete = false) {
756
+ const source = Array.isArray(value) ? value : asObject(value, "knowledge operation must be an array or object with requestList").requestList;
757
+ if (!Array.isArray(source) || !source.length) throw new Error("knowledge operation requires a non-empty array");
758
+ const supportedOperations = /* @__PURE__ */ new Set(["DELETE", "MOVE", "PUBLISH", "UNPUBLISH", "VISIBLE", "INVISIBLE"]);
759
+ const requestList = [];
760
+ for (const [index, value2] of source.entries()) {
761
+ const row = asObject(value2, `knowledge operation #${index + 1} must be an object`);
762
+ const type = knowledgeType(row.knowledgeType);
763
+ const operateType = String(row.operateType ?? "").toUpperCase();
764
+ if (!supportedOperations.has(operateType)) throw new Error(`unsupported knowledge operation: ${operateType}`);
765
+ const knowledgeId = asPositiveInt(row.knowledgeId, `knowledge operation #${index + 1} requires knowledgeId`);
766
+ await this.getKnowledgeByType(String(knowledgeId), type);
767
+ const normalized = { knowledgeType: type, knowledgeId, operateType };
768
+ if (operateType === "MOVE") normalized.directoryId = asPositiveInt(row.directoryId, "MOVE requires directoryId");
769
+ requestList.push(normalized);
770
+ }
771
+ if (requestList.some((item) => item.operateType === "DELETE") && !confirmDelete) throw new Error("DELETE requires --confirm-delete after explicit user acknowledgement");
772
+ return this.post(`${this.qamPrefix}/project/${await this.projectId()}/updateKnowledge`, { requestList });
502
773
  }
503
774
  async listDirectories() {
504
775
  return this.get(`${this.qamPrefix}/project/${await this.projectId()}/directories`, { keyword: "", kuPublished: "" });
@@ -515,12 +786,111 @@ var SolveaClient = class {
515
786
  async knowledgeTags(keyword = "") {
516
787
  return this.get(`${this.qamPrefix}/project/${await this.projectId()}/searchKeywordAndIntents`, { keyword });
517
788
  }
789
+ async moveDirectory(id, parentId) {
790
+ return this.get(`${this.qamPrefix}/project/${await this.projectId()}/directory/move`, { currentId: asPositiveInt(id, "directory id must be a positive integer"), parentId: Number(parentId) });
791
+ }
792
+ async getKnowledgeIsolationKeywords(id, type) {
793
+ return this.get(`${this.qamPrefix}/project/${await this.projectId()}/knowledge/isolation_keywords`, { knowledgeType: isolationKnowledgeType(type), knowledgeId: asPositiveInt(id, "knowledge id must be a positive integer") });
794
+ }
795
+ async batchUpdateIsolationKeywordBindings(data3) {
796
+ const items = data3.items ?? [], directoryIds = data3.directoryIds ?? [], keywordIds = data3.keywordIds ?? [];
797
+ if (!Array.isArray(items) || !Array.isArray(directoryIds) || !Array.isArray(keywordIds)) throw new Error("items, directoryIds and keywordIds must be arrays");
798
+ if (!items.length && !directoryIds.length && !keywordIds.length) throw new Error("isolation keyword binding update has no targets or keyword IDs");
799
+ if (items.length && directoryIds.length) throw new Error("submit knowledge items and directory IDs in separate binding updates");
800
+ const normalizedItems = items.map((value, index) => {
801
+ const row = asObject(value, `isolation binding item #${index + 1} must be an object`);
802
+ if (!Array.isArray(row.keywordIds)) throw new Error("items[].keywordIds must be an array");
803
+ return {
804
+ knowledgeType: isolationKnowledgeType(row.knowledgeType ?? "QA"),
805
+ knowledgeId: asPositiveInt(row.knowledgeId, "items[].knowledgeId must be a positive integer"),
806
+ keywordIds: [...new Set(row.keywordIds.map((value2) => asPositiveInt(value2, "items[].keywordIds must contain positive integer IDs")))]
807
+ };
808
+ });
809
+ return this.post(`${this.qamPrefix}/project/${await this.projectId()}/knowledge/isolation_keywords/batch`, {
810
+ items: normalizedItems,
811
+ directoryIds: [...new Set(directoryIds.map((value) => asPositiveInt(value, "directoryIds must contain positive integer IDs")))],
812
+ keywordIds: [...new Set(keywordIds.map((value) => asPositiveInt(value, "keywordIds must contain positive integer IDs")))]
813
+ });
814
+ }
815
+ async applyIsolationKeywordBindings(data3) {
816
+ const operation = String(data3.operation ?? "append").toLowerCase();
817
+ if (!["append", "remove", "replace"].includes(operation)) throw new Error("operation must be append, remove, or replace");
818
+ const items = data3.items ?? [], directoryIds = data3.directoryIds ?? [];
819
+ if (!Array.isArray(items) || !Array.isArray(directoryIds) || !items.length && !directoryIds.length) throw new Error("binding update requires knowledge items or directory IDs");
820
+ if (items.length && directoryIds.length) throw new Error("submit knowledge items and directory IDs in separate binding updates");
821
+ if (directoryIds.length && operation !== "replace") throw new Error("directory bindings require operation=replace");
822
+ if ((directoryIds.length || operation === "replace") && data3.confirmReplace !== true) throw new Error("binding replacement requires confirmReplace=true after explicit user confirmation");
823
+ const resolvedItems = [];
824
+ for (const [index, value] of items.entries()) {
825
+ const row = asObject(value, `isolation binding item #${index + 1} must be an object`);
826
+ const id = asPositiveInt(row.knowledgeId, "items[].knowledgeId must be a positive integer");
827
+ const type = knowledgeType(row.knowledgeType);
828
+ if (!Array.isArray(row.keywordIds)) throw new Error("items[].keywordIds must be an array");
829
+ const requestedIds = row.keywordIds.map((value2) => asPositiveInt(value2, "items[].keywordIds must contain positive integer IDs"));
830
+ await this.getKnowledgeByType(String(id), type);
831
+ const existingResponse = await this.getKnowledgeIsolationKeywords(String(id), type);
832
+ const existingRows = Array.isArray(existingResponse) ? existingResponse : existingResponse && typeof existingResponse === "object" && Array.isArray(existingResponse.list) ? existingResponse.list : [];
833
+ const existingIds = existingRows.map((value2) => asPositiveInt(asObject(value2, "existing isolation keyword binding must be an object").id, "existing isolation keyword binding has an invalid ID"));
834
+ const finalIds = operation === "append" ? [.../* @__PURE__ */ new Set([...existingIds, ...requestedIds])] : operation === "remove" ? existingIds.filter((id2) => !requestedIds.includes(id2)) : [...new Set(requestedIds)];
835
+ resolvedItems.push({ knowledgeType: type, knowledgeId: id, keywordIds: finalIds });
836
+ }
837
+ for (const value of directoryIds) await this.listKnowledge({ directoryId: asPositiveInt(value, "directoryIds must contain positive integer IDs"), pageIndex: 1, pageSize: 1 });
838
+ return this.batchUpdateIsolationKeywordBindings({ items: resolvedItems, directoryIds, keywordIds: data3.keywordIds ?? [] });
839
+ }
840
+ async editKnowledgeTags(data3) {
841
+ const knowledgeIds = data3.knowledgeIds, requestedTags = data3.tagList;
842
+ if (!Array.isArray(knowledgeIds) || !knowledgeIds.length) throw new Error("edit knowledge tags requires non-empty knowledgeIds");
843
+ if (!Array.isArray(requestedTags) || requestedTags.some((value) => typeof value !== "string" || !value.trim())) throw new Error("tagList must contain non-empty strings");
844
+ const operation = String(data3.operation ?? "append");
845
+ if (!["append", "replace", "remove"].includes(operation)) throw new Error("tag operation must be append, replace, or remove");
846
+ const type = knowledgeType(data3.type ?? "QA");
847
+ const results = [];
848
+ for (const value of knowledgeIds) {
849
+ const id = asPositiveInt(value, "knowledgeIds must contain positive integer IDs");
850
+ const current = asObject(await this.getKnowledgeByType(String(id), type), `could not read knowledge ${id} before tag update`);
851
+ const metadata = current.metadata && typeof current.metadata === "object" && !Array.isArray(current.metadata) ? current.metadata : {};
852
+ const existing = Array.isArray(current.tags) ? current.tags : Array.isArray(metadata.tags) ? metadata.tags : [];
853
+ const requested = requestedTags.map(String);
854
+ const tagList = operation === "append" ? [.../* @__PURE__ */ new Set([...existing.map(String), ...requested])] : operation === "remove" ? existing.map(String).filter((tag) => !requested.includes(tag)) : [...new Set(requested)];
855
+ results.push(await this.post(`${this.qamPrefix}/project/${await this.projectId()}/editTags`, { type, knowledgeIds: [id], tagList }));
856
+ }
857
+ return { success: true, results };
858
+ }
518
859
  async transcript(chatId) {
519
860
  return this.get(`/api_v2/gpt/project/${await this.projectId()}/transcript/${encodeURIComponent(chatId)}`, { isCompare: true });
520
861
  }
521
862
  async listTranscripts(data3) {
522
863
  return this.post(`/api_v2/gpt/project/${await this.projectId()}/transcripts/filter`, data3);
523
864
  }
865
+ async getConversationsByIntent(intent, limit = 50) {
866
+ const intentResponse = await this.listIntents("");
867
+ const intents = Array.isArray(intentResponse) ? intentResponse : intentResponse && typeof intentResponse === "object" && Array.isArray(intentResponse.data) ? intentResponse.data : intentResponse && typeof intentResponse === "object" && Array.isArray(intentResponse.list) ? intentResponse.list : [];
868
+ const matched = intents.find((value) => value && typeof value === "object" && String(value.name ?? "") === intent);
869
+ if (!matched?.id) return [];
870
+ const response = await this.listTranscripts({ pageSize: limit, pageIndex: 1, intentId: Number(matched.id) });
871
+ const rows = Array.isArray(response) ? response : response && typeof response === "object" && Array.isArray(response.list) ? response.list : [];
872
+ const result = [];
873
+ for (const value of rows) {
874
+ if (!value || typeof value !== "object") continue;
875
+ const row = value, chatId = row.chatId ?? row.id;
876
+ if (!chatId) continue;
877
+ try {
878
+ const detail = asObject(await this.transcript(String(chatId)), `transcript ${chatId} must be an object`);
879
+ const turns = Array.isArray(detail.turns) ? detail.turns : Array.isArray(detail.messages) ? detail.messages : [];
880
+ const messages = [];
881
+ let metadata = {};
882
+ for (const turnValue of turns) {
883
+ if (!turnValue || typeof turnValue !== "object") continue;
884
+ const turn = turnValue, role = turn.role ?? turn.type, content = turn.content ?? turn.message;
885
+ if (role === "USER" && content) messages.push(content);
886
+ if (!Object.keys(metadata).length && turn.metadata && typeof turn.metadata === "object" && !Array.isArray(turn.metadata)) metadata = turn.metadata;
887
+ }
888
+ if (messages.length) result.push({ source_chat_id: chatId, intent, messages, metadata });
889
+ } catch {
890
+ }
891
+ }
892
+ return result;
893
+ }
524
894
  async resolveTranscriptChatId(externalId, pageSize2 = 10) {
525
895
  const result = await this.listTranscripts({ pageSize: pageSize2, pageIndex: 1, debug: false, keyword: externalId });
526
896
  const rows = Array.isArray(result) ? result : result?.list;
@@ -535,10 +905,10 @@ var SolveaClient = class {
535
905
  };
536
906
 
537
907
  // src/solvea/input.ts
538
- import { readFile } from "fs/promises";
908
+ import { readFile as readFile2 } from "fs/promises";
539
909
  async function jsonData(args, required = true) {
540
910
  const file = option(args, "--data-file");
541
- const raw = file ? await readFile(file, "utf8") : option(args, "--data");
911
+ const raw = file ? await readFile2(file, "utf8") : option(args, "--data");
542
912
  if (!raw) {
543
913
  if (required) throw new Error("--data or --data-file is required");
544
914
  return {};
@@ -549,7 +919,7 @@ async function jsonData(args, required = true) {
549
919
  }
550
920
  async function jsonValue(args) {
551
921
  const file = option(args, "--data-file");
552
- const raw = file ? await readFile(file, "utf8") : option(args, "--data", true);
922
+ const raw = file ? await readFile2(file, "utf8") : option(args, "--data", true);
553
923
  return JSON.parse(raw);
554
924
  }
555
925
  function requireFlag(args, name, command) {
@@ -649,9 +1019,9 @@ var reportBody = async (args) => ({
649
1019
  ...await data(args)
650
1020
  });
651
1021
  var handlers2 = {
652
- "chat-stream": async (c, a) => c.rawCall("POST", `/api_v2/gpt/bot/${await c.botId()}/chat`, { message: option(a, "--message", true), botId: Number(option(a, "--bot-id") ?? await c.botId()), projectId: Number(await project(c)), option: { debug: true }, stream: true }),
1022
+ "chat-stream": async (c, a) => c.chatStream({ message: option(a, "--message", true), botId: Number(option(a, "--bot-id") ?? await c.botId()), touchPoint: option(a, "--touch-point") ?? "LIVE_CHAT", ...await data(a) }),
653
1023
  "update-chatflow": async (c, a) => c.updateChatflow(option(a, "--agent-id", true), await jsonData(a), flag(a, "--publish")),
654
- "get-conversations-by-intent": async (c, a) => c.listTranscripts({ pageSize: Number(option(a, "--limit") ?? 50), pageIndex: 1, intent: option(a, "--intent", true) }),
1024
+ "get-conversations-by-intent": (c, a) => c.getConversationsByIntent(option(a, "--intent", true), Number(option(a, "--limit") ?? 50)),
655
1025
  "resolve-transcript-chat-id": async (c, a) => c.resolveTranscriptChatId(option(a, "--external-id", true), Number(option(a, "--page-size") ?? 10)),
656
1026
  "list-isolation-keywords": async (c, a) => c.rawCall("GET", `${c.qamPath}/project/${await project(c)}/isolation_keywords`, void 0, { keyword: option(a, "--keyword") ?? "", pageIndex: page(a), pageSize: pageSize(a) }),
657
1027
  "create-isolation-keyword": async (c, a) => c.rawCall("POST", `${c.qamPath}/project/${await project(c)}/isolation_keyword`, { keyword: option(a, "--keyword", true) }),
@@ -660,16 +1030,16 @@ var handlers2 = {
660
1030
  requireFlag(a, "--confirm-delete", "delete-isolation-keyword");
661
1031
  return c.rawCall("DELETE", `${c.qamPath}/project/${await project(c)}/isolation_keyword/${requireId(a)}`);
662
1032
  },
663
- "get-knowledge-isolation-keywords": async (c, a) => c.rawCall("GET", `${c.qamPath}/project/${await project(c)}/knowledge/isolation_keywords`, void 0, { knowledgeType: option(a, "--type", true), knowledgeId: requireId(a) }),
664
- "batch-update-isolation-keyword-bindings": async (c, a) => c.rawCall("POST", `${c.qamPath}/project/${await project(c)}/knowledge/isolation_keywords/batch`, await jsonValue(a)),
665
- "edit-knowledge-tags": async (c, a) => c.rawCall("POST", `${c.qamPath}/project/${await project(c)}/editTags`, await jsonValue(a)),
666
- "move-directory": async (c, a) => c.rawCall("GET", `${c.qamPath}/project/${await project(c)}/directory/move`, void 0, { currentId: requireId(a), parentId: option(a, "--parent-id", true) }),
667
- "operate-knowledge": async (c, a) => c.rawCall("POST", `${c.qamPath}/project/${await project(c)}/updateKnowledge`, await jsonValue(a)),
668
- "batch-update-knowledge": async (c, a) => c.rawCall("PUT", `${c.qamPath}/project/${await project(c)}/kus`, await jsonValue(a)),
669
- "update-document-content": async (c, a) => c.rawCall("PUT", `${c.qamPath}/project/${await project(c)}/document`, { id: requireId(a), ...await data(a) }),
670
- "create-product-knowledge": async (c, a) => c.rawCall("POST", `${c.qamPath}/project/${await project(c)}/ku_product`, await jsonValue(a)),
671
- "update-product-knowledge": async (c, a) => c.rawCall("PUT", `${c.qamPath}/project/${await project(c)}/ku_product`, { kuProductId: Number(requireId(a)), ...await data(a) }),
672
- "batch-update-products": async (c, a) => c.rawCall("PUT", `${c.qamPath}/project/${await project(c)}/ku_products`, { list: await jsonValue(a) }),
1033
+ "get-knowledge-isolation-keywords": (c, a) => c.getKnowledgeIsolationKeywords(requireId(a), option(a, "--type", true)),
1034
+ "batch-update-isolation-keyword-bindings": async (c, a) => c.applyIsolationKeywordBindings(await data(a)),
1035
+ "edit-knowledge-tags": async (c, a) => c.editKnowledgeTags(await data(a)),
1036
+ "move-directory": (c, a) => c.moveDirectory(requireId(a), option(a, "--parent-id", true)),
1037
+ "operate-knowledge": async (c, a) => c.operateKnowledge(await jsonValue(a), flag(a, "--confirm-delete")),
1038
+ "batch-update-knowledge": async (c, a) => c.updateKus(await jsonValue(a)),
1039
+ "update-document-content": async (c, a) => c.updateDocumentContent(requireId(a), option(a, "--type", true), await data(a)),
1040
+ "create-product-knowledge": async (c, a) => c.createProductKnowledge(await data(a)),
1041
+ "update-product-knowledge": async (c, a) => c.updateProductKnowledge(requireId(a), await data(a)),
1042
+ "batch-update-products": async (c, a) => c.updateKuProducts(await jsonValue(a)),
673
1043
  "upload-web": async (c, a) => c.rawCall("POST", `${c.qamPath}/project/${await project(c)}/documents`, { documents: optionList(a, "--url").map((filePath) => ({ filePath, docType: "TC", fileType: "website" })), taskType: "WEB", published: true, isVisible: true, metadata: { tags: [] }, directoryId: option(a, "--directory-id") ?? "" }),
674
1044
  "list-label-groups": async (c, a) => c.rawCall("GET", `/api_v2/gpt/label/project/${await project(c)}/getTreeInfo`, void 0, option(a, "--name") ? { treeName: option(a, "--name") } : void 0),
675
1045
  "create-label-group": async (c, a) => c.rawCall("POST", `/api_v2/gpt/label/project/${await project(c)}/updateTreeInfo`, { ...await data(a), treeName: option(a, "--name", true), operateType: "CREATE" }),
@@ -686,12 +1056,12 @@ var handlers2 = {
686
1056
  requireFlag(a, "--confirm-delete", "delete-label-node");
687
1057
  return c.rawCall("POST", `/api_v2/gpt/label/project/${await project(c)}/updateNode`, { treeId: Number(option(a, "--tree-id", true)), operateType: "DELETE", nodes: [{ id: Number(requireId(a)) }] });
688
1058
  },
689
- "download-label-template": async (c, a) => c.rawCall("GET", `/api_v2/gpt/label/project/${await project(c)}/downloadLabelTemplate`, void 0, { type: option(a, "--template-type") ?? "VOC_LABEL" }),
690
- "download-labels": async (c, a) => c.rawCall("GET", `/api_v2/gpt/label/project/${await project(c)}/downloadLabel`, void 0, { treeId: option(a, "--tree-id", true) }),
691
- "upload-labels": async (c, a) => c.rawCall("POST", `/api_v2/gpt/label/project/${await project(c)}/uploadLabel`, { treeId: Number(option(a, "--tree-id", true)), file: option(a, "--file", true), ...option(a, "--init") ? { uploadWay: "INIT" } : {} }),
1059
+ "download-label-template": async (c, a) => ({ file: await c.downloadLabelTemplate(option(a, "--template-type") === "init" ? "VOC_LABEL_INIT" : "VOC_LABEL", option(a, "--output") ?? `${option(a, "--template-type") ?? "label"}_template.xlsx`) }),
1060
+ "download-labels": async (c, a) => ({ file: await c.downloadLabels(option(a, "--tree-id", true), option(a, "--output") ?? `labels_${option(a, "--tree-id", true)}.xlsx`) }),
1061
+ "upload-labels": (c, a) => c.uploadLabelFile(option(a, "--tree-id", true), option(a, "--file", true), flag(a, "--init")),
692
1062
  "export-transcripts": async (c, a) => c.rawCall("POST", `/api_v2/gpt/project/${await project(c)}/transcripts/export/async`, { pageSize: pageSize(a), pageIndex: page(a), timeOffset: Number(option(a, "--time-offset") ?? 8), ...await data(a) }),
693
1063
  "get-export-tasks": async (c, a) => c.rawCall("POST", `/api_v2/gpt/label/project/${await project(c)}/getLabelTask`, { page: page(a), pageSize: pageSize(a) }),
694
- "upload-batch-test-file": async (c, a) => c.rawCall("GET", `/api_v2/gpt/project/${await project(c)}/sts_credentials`),
1064
+ "upload-batch-test-file": async (c, a) => ({ file: await c.uploadTempFile(option(a, "--file", true)) }),
695
1065
  "submit-batch-test": async (c, a) => c.rawCall("POST", `/api_v2/gpt/label/project/${await project(c)}/uploadMessage`, { file: option(a, "--file", true), type: option(a, "--type", true), ...option(a, "--bot-id") ? { botId: Number(option(a, "--bot-id")) } : {}, ...await data(a) }),
696
1066
  "get-batch-test-tasks": async (c, a) => c.rawCall("POST", `/api_v2/gpt/label/project/${await project(c)}/getLabelTask`, { page: page(a), pageSize: pageSize(a), ...option(a, "--type") ? { taskType: option(a, "--type") } : {} }),
697
1067
  "list-voice-agents": (c) => c.listAgents("voice"),
@@ -732,7 +1102,7 @@ function enabled(args, command) {
732
1102
  }
733
1103
  async function data2(args) {
734
1104
  const file = option(args, "--data-file");
735
- const raw = file ? await readFile2(file, "utf8") : option(args, "--data");
1105
+ const raw = file ? await readFile3(file, "utf8") : option(args, "--data");
736
1106
  if (!raw) throw new Error("--data or --data-file is required");
737
1107
  const parsed = JSON.parse(raw);
738
1108
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("request data must be a JSON object");
@@ -858,7 +1228,7 @@ async function runSolveaCommand(session, command, args) {
858
1228
  case "list-knowledge":
859
1229
  return client.listKnowledge(await knowledgeFilters(args));
860
1230
  case "get-knowledge":
861
- return client.getKnowledge(id());
1231
+ return client.getKnowledgeByType(id(), option(args, "--type", true));
862
1232
  case "update-knowledge":
863
1233
  return client.updateKnowledge(id(), await data2(args));
864
1234
  case "upload-qa":
@@ -939,7 +1309,7 @@ function isNativeSolveaCommand(command) {
939
1309
  }
940
1310
 
941
1311
  // src/business.ts
942
- var packageRoot = () => resolve(dirname(fileURLToPath(import.meta.url)), "..");
1312
+ var packageRoot = () => resolve(dirname2(fileURLToPath(import.meta.url)), "..");
943
1313
  async function runApi(session, command, args) {
944
1314
  if (isNativeSolveaCommand(command)) {
945
1315
  console.log(JSON.stringify(await runSolveaCommand(session, command, args), null, 2));
@@ -1000,7 +1370,7 @@ var resources = {
1000
1370
  },
1001
1371
  knowledge: {
1002
1372
  list: action("list-knowledge", "List knowledge", "[--type <type>] [--page <number>] [--page-size <number>] [--keyword <text>] [--data <json> | --data-file <path>]"),
1003
- get: action("get-knowledge", "Get one knowledge item", "--id <knowledge-id>"),
1373
+ get: action("get-knowledge", "Get one knowledge item", "--id <knowledge-id> --type <QA|LEARN_SELF|FILE|WEBSITE|PRODUCT>"),
1004
1374
  update: action("update-knowledge", "Update a knowledge item", "--id <knowledge-id> (--data <json> | --data-file <path>)"),
1005
1375
  "upload-qa": action("upload-qa", "Upload QA items", "--data <json> | --data-file <path>"),
1006
1376
  tags: action("list-knowledge-tags", "List knowledge tags", "[--keyword <keyword>]")
@@ -1135,7 +1505,7 @@ function publicTenant(t) {
1135
1505
 
1136
1506
  // src/update.ts
1137
1507
  import { spawn } from "child_process";
1138
- import { mkdir, writeFile } from "fs/promises";
1508
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
1139
1509
  import { homedir, platform } from "os";
1140
1510
  import { isAbsolute, join as join2 } from "path";
1141
1511
  var exec = (command, args, options3 = {}) => new Promise((resolve5, reject) => {
@@ -1213,8 +1583,8 @@ async function installScheduler() {
1213
1583
  const dir = join2(homedir(), "Library/LaunchAgents");
1214
1584
  const path = join2(dir, "com.solvea.cxclaw.update.plist");
1215
1585
  const programArguments = [invocation.command, ...invocation.args].map((value) => `<string>${xml(value)}</string>`).join("");
1216
- await mkdir(dir, { recursive: true });
1217
- await writeFile(
1586
+ await mkdir2(dir, { recursive: true });
1587
+ await writeFile2(
1218
1588
  path,
1219
1589
  `<?xml version="1.0" encoding="UTF-8"?>
1220
1590
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -1228,15 +1598,15 @@ async function installScheduler() {
1228
1598
  if (platform() === "linux") {
1229
1599
  const dir = join2(homedir(), ".config/systemd/user");
1230
1600
  const command = [invocation.command, ...invocation.args].map(systemdArg).join(" ");
1231
- await mkdir(dir, { recursive: true });
1232
- await writeFile(
1601
+ await mkdir2(dir, { recursive: true });
1602
+ await writeFile2(
1233
1603
  join2(dir, "cxclaw-update.service"),
1234
1604
  `[Service]
1235
1605
  Type=oneshot
1236
1606
  ExecStart=${command}
1237
1607
  `
1238
1608
  );
1239
- await writeFile(
1609
+ await writeFile2(
1240
1610
  join2(dir, "cxclaw-update.timer"),
1241
1611
  "[Timer]\nOnBootSec=5m\nOnUnitActiveSec=6h\nPersistent=true\n[Install]\nWantedBy=timers.target\n"
1242
1612
  );
@@ -1387,14 +1757,14 @@ async function runRuntime(action2, args) {
1387
1757
  // src/workflows/solvea-kit.ts
1388
1758
  import ExcelJS from "exceljs";
1389
1759
  import { promises as fs2 } from "fs";
1390
- import { dirname as dirname2, join as join4, resolve as resolve3 } from "path";
1760
+ import { dirname as dirname3, join as join4, resolve as resolve3 } from "path";
1391
1761
  var workspace2 = () => resolve3(process.env.WORKSPACE_DIR ?? join4(process.cwd(), "workspace"));
1392
1762
  var outputDir = (owner) => join4(workspace2(), "outputs", owner);
1393
1763
  var stateDir = (owner) => join4(workspace2(), "state", owner);
1394
1764
  var now = () => (/* @__PURE__ */ new Date()).toISOString().replace(/[-:TZ.]/g, "").slice(0, 15);
1395
1765
  var readJson2 = async (path) => JSON.parse(await fs2.readFile(path, "utf8"));
1396
1766
  var writeJson2 = async (path, value) => {
1397
- await fs2.mkdir(dirname2(path), { recursive: true });
1767
+ await fs2.mkdir(dirname3(path), { recursive: true });
1398
1768
  await fs2.writeFile(path, JSON.stringify(value, null, 2));
1399
1769
  };
1400
1770
  var INIT_NOTICE = `The information of this row cannot be deleted! Tips:
@@ -1448,7 +1818,7 @@ async function generateLabelImport(args) {
1448
1818
  sheet.columns.forEach((column) => {
1449
1819
  column.width = 18;
1450
1820
  });
1451
- await fs2.mkdir(dirname2(output), { recursive: true });
1821
+ await fs2.mkdir(dirname3(output), { recursive: true });
1452
1822
  await workbook.xlsx.writeFile(output);
1453
1823
  return { file: output, format: init ? "init" : "update", rows: labels.length };
1454
1824
  }
@@ -1472,8 +1842,8 @@ async function runSolveaKitWorkflow(action2, args) {
1472
1842
  }
1473
1843
 
1474
1844
  // src/workflows/support.ts
1475
- import { readFile as readFile3 } from "fs/promises";
1476
- import { basename as basename2 } from "path";
1845
+ import { readFile as readFile4 } from "fs/promises";
1846
+ import { basename as basename3 } from "path";
1477
1847
  var safe = (value) => encodeURIComponent(value);
1478
1848
  var docsMcpUrl = process.env.CXCLAW_DOCS_MCP_URL ?? "https://solvea-39f11a36.mintlify.app/mcp";
1479
1849
  var publicDocsPrefix = "https://www.voc.ai/cn/docs/";
@@ -1547,7 +1917,7 @@ async function runSupport(session, action2, args) {
1547
1917
  const attachments = [];
1548
1918
  for (const file of options2(args, "--attachment")) {
1549
1919
  const form = new FormData();
1550
- form.append("file", new Blob([await readFile3(file)]), basename2(file));
1920
+ form.append("file", new Blob([await readFile4(file)]), basename3(file));
1551
1921
  const tenant = session.tenant;
1552
1922
  const base = tenant?.businessUrl?.replace(/\/$/, "");
1553
1923
  if (!tenant || !base || !tenant.businessToken) throw new Error("no tenant selected");
@@ -1578,24 +1948,24 @@ async function runSupport(session, action2, args) {
1578
1948
  }
1579
1949
 
1580
1950
  // src/workflows/common.ts
1581
- import { mkdir as mkdir2, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
1582
- import { dirname as dirname3, join as join5, resolve as resolve4 } from "path";
1951
+ import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
1952
+ import { dirname as dirname4, join as join5, resolve as resolve4 } from "path";
1583
1953
  import { homedir as homedir2 } from "os";
1584
1954
  var workspaceRoot = () => resolve4(process.env.CXCLAW_WORKSPACE ?? join5(homedir2(), ".cxclaw", "workspace"));
1585
1955
  var stateRoot = (domain) => resolve4(process.env.CXCLAW_STATE_DIR ?? join5(workspaceRoot(), "state"), domain);
1586
1956
  var outputRoot = (domain) => resolve4(process.env.CXCLAW_OUTPUT_DIR ?? join5(workspaceRoot(), "outputs"), domain);
1587
1957
  async function ensureParent(file) {
1588
- await mkdir2(dirname3(file), { recursive: true });
1958
+ await mkdir3(dirname4(file), { recursive: true });
1589
1959
  }
1590
1960
  async function ensureDir(directory) {
1591
- await mkdir2(directory, { recursive: true });
1961
+ await mkdir3(directory, { recursive: true });
1592
1962
  }
1593
1963
  async function readJson3(file) {
1594
- return JSON.parse(await readFile4(file, "utf8"));
1964
+ return JSON.parse(await readFile5(file, "utf8"));
1595
1965
  }
1596
1966
  async function writeJson3(file, value) {
1597
1967
  await ensureParent(file);
1598
- await writeFile2(file, `${JSON.stringify(value, null, 2)}
1968
+ await writeFile3(file, `${JSON.stringify(value, null, 2)}
1599
1969
  `, "utf8");
1600
1970
  }
1601
1971
  function intOption(args, name, fallback) {
@@ -1613,7 +1983,7 @@ function htmlEscape(value) {
1613
1983
  }
1614
1984
  async function writeHtml(file, title, body) {
1615
1985
  await ensureParent(file);
1616
- await writeFile2(file, `<!doctype html><html><head><meta charset="utf-8"><title>${htmlEscape(title)}</title><style>body{font-family:system-ui,sans-serif;margin:2rem;line-height:1.5}pre{white-space:pre-wrap;background:#f6f8fa;padding:1rem;border-radius:8px}.card{border:1px solid #ddd;border-radius:8px;padding:1rem;margin:1rem 0}</style></head><body><h1>${htmlEscape(title)}</h1>${body}</body></html>`, "utf8");
1986
+ await writeFile3(file, `<!doctype html><html><head><meta charset="utf-8"><title>${htmlEscape(title)}</title><style>body{font-family:system-ui,sans-serif;margin:2rem;line-height:1.5}pre{white-space:pre-wrap;background:#f6f8fa;padding:1rem;border-radius:8px}.card{border:1px solid #ddd;border-radius:8px;padding:1rem;margin:1rem 0}</style></head><body><h1>${htmlEscape(title)}</h1>${body}</body></html>`, "utf8");
1617
1987
  }
1618
1988
  function asRows(value) {
1619
1989
  if (Array.isArray(value)) return value.filter((item) => item && typeof item === "object");
@@ -1820,8 +2190,8 @@ async function runLocalAgentWorkflow(action2, args) {
1820
2190
  }
1821
2191
  async function writeFileText(file, content) {
1822
2192
  await ensureParent(file);
1823
- const { writeFile: writeFile3 } = await import("fs/promises");
1824
- await writeFile3(file, `${content}
2193
+ const { writeFile: writeFile4 } = await import("fs/promises");
2194
+ await writeFile4(file, `${content}
1825
2195
  `, "utf8");
1826
2196
  }
1827
2197
 
@@ -1859,9 +2229,9 @@ async function createSheet(args) {
1859
2229
  return { output, count: cases.length, mode: option(args, "--mode") };
1860
2230
  }
1861
2231
  async function submit(client, args) {
1862
- const file = option(args, "--file", true);
2232
+ const file = await client.uploadTempFile(option(args, "--file", true));
1863
2233
  const result = await client.rawCall("POST", `/api_v2/gpt/label/project/${await client.projectId()}/uploadMessage`, { file, type: option(args, "--mode", true), ...option(args, "--agent-id") ? { agentId: Number(option(args, "--agent-id")) } : {}, ...option(args, "--flow-version") ? { flowVersion: Number(option(args, "--flow-version")) } : {} });
1864
- return result;
2234
+ return { fileKey: file, result };
1865
2235
  }
1866
2236
  async function runAgentTester(session, action2, args) {
1867
2237
  if (!action2) throw new Error("agent-tester action is required");
@@ -2144,6 +2514,56 @@ async function extractWebQa(args) {
2144
2514
  }
2145
2515
  return { outputDir: outputDir2, files, count: files.length };
2146
2516
  }
2517
+ var flattenDirectories = (nodes, result = []) => {
2518
+ for (const node of nodes) {
2519
+ result.push(node);
2520
+ if (Array.isArray(node.children)) flattenDirectories(node.children.filter((child) => child && typeof child === "object"), result);
2521
+ }
2522
+ return result;
2523
+ };
2524
+ function validateQaItems(values, maxDepth) {
2525
+ if (!values.length) throw new Error("QA plan must contain at least one item");
2526
+ return values.map((value, index) => {
2527
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`QA item #${index + 1} must be an object`);
2528
+ const item = value;
2529
+ if (!Array.isArray(item.dir_path) || !item.dir_path.length) throw new Error(`QA item #${index + 1} dir_path must be a non-empty array`);
2530
+ if (item.dir_path.length > maxDepth) throw new Error(`QA item #${index + 1} dir_path exceeds maximum depth ${maxDepth}`);
2531
+ const dirPath = item.dir_path.map((name, depth) => {
2532
+ if (typeof name !== "string" || !name.trim() || /[\\/]/.test(name)) throw new Error(`QA item #${index + 1} dir_path[${depth}] must be a folder name`);
2533
+ return name.trim();
2534
+ });
2535
+ const title = typeof item.question === "string" ? item.question.trim() : "";
2536
+ const content = typeof item.answer === "string" ? item.answer.trim() : "";
2537
+ if (!title) throw new Error(`QA item #${index + 1} question must be a non-empty string`);
2538
+ if (!content) throw new Error(`QA item #${index + 1} answer must be a non-empty markdown string`);
2539
+ return { dirPath, title, content };
2540
+ });
2541
+ }
2542
+ async function resolveDirectory(client, path, directories, cache) {
2543
+ let parentId = 0;
2544
+ const prefix = [];
2545
+ for (const name of path) {
2546
+ prefix.push(name);
2547
+ const key = prefix.join("/");
2548
+ const cached = cache.get(key);
2549
+ if (cached !== void 0) {
2550
+ parentId = cached;
2551
+ continue;
2552
+ }
2553
+ const match = directories.find((directory) => String(directory.name ?? "") === name && Number(directory.parentId ?? 0) === parentId);
2554
+ if (match) parentId = Number(match.id ?? match.directoryId);
2555
+ else {
2556
+ const createdParentId = parentId;
2557
+ const created = await client.createFolder(name, parentId);
2558
+ const id = Number(created?.id ?? created?.directoryId);
2559
+ if (!Number.isSafeInteger(id) || id <= 0) throw new Error(`could not create directory ${key}: response has no directory id`);
2560
+ parentId = id;
2561
+ directories.push({ id, name, parentId: Number(created?.parentId ?? createdParentId) });
2562
+ }
2563
+ cache.set(key, parentId);
2564
+ }
2565
+ return parentId;
2566
+ }
2147
2567
  async function importQa(client, args) {
2148
2568
  const qaFile = option(args, "--qa-file");
2149
2569
  const fromDir = option(args, "--from-dir");
@@ -2155,23 +2575,67 @@ async function importQa(client, args) {
2155
2575
  }
2156
2576
  if (fromDir) {
2157
2577
  const { readdir } = await import("fs/promises");
2158
- for (const file of (await readdir(fromDir)).filter((name) => name.endsWith(".json"))) {
2578
+ for (const file of (await readdir(fromDir)).filter((name) => name.endsWith(".json")).sort()) {
2159
2579
  const value = await readJson3(join8(fromDir, file));
2160
2580
  items.push(...Array.isArray(value) ? value : value.items && Array.isArray(value.items) ? value.items : [value]);
2161
2581
  }
2162
2582
  }
2583
+ const maxDepth = intOption(args, "--max-depth", 3);
2584
+ if (!Number.isSafeInteger(maxDepth) || maxDepth <= 0) throw new Error("--max-depth must be a positive integer");
2585
+ const normalized = validateQaItems(items, maxDepth);
2163
2586
  const plan2 = { items, count: items.length, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
2164
2587
  const planFile = join8(outputRoot("kb-builder"), `qa_import_plan_${Date.now()}.json`);
2165
2588
  await writeJson3(planFile, plan2);
2166
2589
  if (flag(args, "--dry-run")) return { dryRun: true, planFile, count: items.length };
2167
- return { planFile, count: items.length, result: await client.uploadQa(items) };
2590
+ const directories = flattenDirectories(asRows(await client.listDirectories()));
2591
+ const cache = /* @__PURE__ */ new Map();
2592
+ const payload = [];
2593
+ for (const item of normalized) payload.push({ title: item.title, content: item.content, directoryId: await resolveDirectory(client, item.dirPath, directories, cache) });
2594
+ return { planFile, count: payload.length, result: await client.uploadQa(payload) };
2168
2595
  }
2169
2596
  async function importSources(client, args) {
2170
- const plan2 = await readJson3(option(args, "--plan-file", true));
2171
- const documents = Array.isArray(plan2.documents) ? plan2.documents : Array.isArray(plan2.items) ? plan2.items : [];
2172
- const payload = { documents, taskType: "WEB", published: true, isVisible: true, metadata: { tags: [] }, directoryId: String(plan2.directoryId ?? "") };
2173
- if (!flag(args, "--execute")) return { dryRun: true, payload };
2174
- return client.rawCall("POST", `${client.qamPath}/project/${await client.projectId()}/documents`, payload);
2597
+ const value = await readJson3(option(args, "--plan-file", true));
2598
+ const source = Array.isArray(value) ? value : value && typeof value === "object" && Array.isArray(value.items) ? value.items : [];
2599
+ if (!source.length) throw new Error("source document plan must be a non-empty array");
2600
+ const maxDepth = intOption(args, "--max-depth", 3);
2601
+ if (!Number.isSafeInteger(maxDepth) || maxDepth <= 0) throw new Error("--max-depth must be a positive integer");
2602
+ const { stat } = await import("fs/promises");
2603
+ const normalized = [];
2604
+ const seenUrls = /* @__PURE__ */ new Set();
2605
+ for (const [index, raw] of source.entries()) {
2606
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error(`source document #${index + 1} must be an object`);
2607
+ const item = raw;
2608
+ const itemSource = typeof item.source === "string" ? item.source.trim() : "";
2609
+ if (!itemSource) throw new Error(`source document #${index + 1} needs a non-empty source`);
2610
+ const path = item.dir_path ?? [];
2611
+ if (!Array.isArray(path) || path.some((name) => typeof name !== "string" || !name.trim() || /[\\/]/.test(name))) throw new Error(`source document #${index + 1} dir_path must contain folder names`);
2612
+ if (path.length > maxDepth) throw new Error(`source document #${index + 1} dir_path exceeds maximum depth ${maxDepth}`);
2613
+ const isUrl = /^https?:\/\//i.test(itemSource);
2614
+ if (!isUrl) {
2615
+ if (![".doc", ".docx", ".pdf"].some((extension) => itemSource.toLowerCase().endsWith(extension))) throw new Error(`source document #${index + 1} supports .doc, .docx, or .pdf`);
2616
+ const info = await stat(itemSource).catch(() => void 0);
2617
+ if (!info?.isFile()) throw new Error(`source document #${index + 1} file not found: ${itemSource}`);
2618
+ }
2619
+ const skippedDuplicate = isUrl && seenUrls.has(itemSource);
2620
+ if (isUrl) seenUrls.add(itemSource);
2621
+ normalized.push({ source: itemSource, dirPath: path.map(String).map((name) => name.trim()), isUrl, skippedDuplicate });
2622
+ }
2623
+ if (!flag(args, "--execute")) return { dryRun: true, count: normalized.length, items: normalized.map((item) => ({ source: item.source, dir_path: item.dirPath, status: item.skippedDuplicate ? "skipped_duplicate" : "pending" })) };
2624
+ const directories = flattenDirectories(asRows(await client.listDirectories()));
2625
+ const cache = /* @__PURE__ */ new Map();
2626
+ const results = [];
2627
+ for (const item of normalized) {
2628
+ if (item.skippedDuplicate) {
2629
+ results.push({ source: item.source, status: "skipped_duplicate" });
2630
+ continue;
2631
+ }
2632
+ const directoryId = await resolveDirectory(client, item.dirPath, directories, cache);
2633
+ const filePath = item.isUrl ? item.source : await client.uploadKnowledgeFile(item.source);
2634
+ const documents = [{ filePath, docType: "TC", ...item.isUrl ? { fileType: "website" } : {} }];
2635
+ const result = await client.createDocuments(documents, directoryId, item.isUrl ? "WEB" : "FILE");
2636
+ results.push({ source: item.source, directoryId, status: "submitted", result });
2637
+ }
2638
+ return { count: normalized.length, results };
2175
2639
  }
2176
2640
  async function importTemplates(client, args) {
2177
2641
  const source = option(args, "--plan-file") ?? option(args, "--xlsx");
@@ -2449,7 +2913,7 @@ async function main(args = process.argv.slice(2)) {
2449
2913
  if (flag(args, "--scheduled")) await new Promise((resolve5) => setTimeout(resolve5, Math.random() * 18e5));
2450
2914
  return print(await update());
2451
2915
  }
2452
- if (command === "commands" || command === "capabilities") console.log(await readFile5(new URL("../capabilities.lock.json", import.meta.url), "utf8"));
2916
+ if (command === "commands" || command === "capabilities") console.log(await readFile6(new URL("../capabilities.lock.json", import.meta.url), "utf8"));
2453
2917
  else throw new Error(`unknown command: ${command}`);
2454
2918
  }
2455
2919
  main().catch((error) => {