@shulex/cxclawctl 0.1.9 → 0.1.11
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/README.md +5 -31
- package/capabilities.lock.json +2 -2
- package/dist/cli.js +157 -113
- package/dist/cli.js.map +1 -1
- package/package.json +3 -1
- package/runtime/auth-wait-state.d.mts +6 -0
- package/runtime/auth-wait-state.mjs +56 -0
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
|
|
4
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
5
5
|
|
|
6
6
|
// src/auth.ts
|
|
7
7
|
import open from "open";
|
|
@@ -12,7 +12,7 @@ var AUTH_SERVER_URL = "https://ai.solvea.cx/api_v2/ticket";
|
|
|
12
12
|
// package.json
|
|
13
13
|
var package_default = {
|
|
14
14
|
name: "@shulex/cxclawctl",
|
|
15
|
-
version: "0.1.
|
|
15
|
+
version: "0.1.11",
|
|
16
16
|
description: "Solvea CXClaw CLI and Codex plugin runtime",
|
|
17
17
|
type: "module",
|
|
18
18
|
bin: {
|
|
@@ -22,6 +22,7 @@ var package_default = {
|
|
|
22
22
|
"bin",
|
|
23
23
|
"dist/cli.js",
|
|
24
24
|
"dist/cli.js.map",
|
|
25
|
+
"runtime",
|
|
25
26
|
"capabilities.lock.json"
|
|
26
27
|
],
|
|
27
28
|
scripts: {
|
|
@@ -33,6 +34,7 @@ var package_default = {
|
|
|
33
34
|
"check:release": "tsx scripts/check-release.ts",
|
|
34
35
|
"test:package": "tsx scripts/test-packed-package.ts",
|
|
35
36
|
"release:prepare": "tsx scripts/prepare-release.ts",
|
|
37
|
+
"release:create": "tsx scripts/create-release.ts",
|
|
36
38
|
"package:agent-plugin": "node scripts/package-agent-plugin.mjs",
|
|
37
39
|
"package:codex-plugin": "node scripts/package-codex-plugin.mjs"
|
|
38
40
|
},
|
|
@@ -131,40 +133,82 @@ async function deleteSession() {
|
|
|
131
133
|
await entry().deleteCredential();
|
|
132
134
|
}
|
|
133
135
|
|
|
136
|
+
// runtime/auth-wait-state.mjs
|
|
137
|
+
import { createHash } from "crypto";
|
|
138
|
+
import { mkdir, readFile, readdir, rm, writeFile } from "fs/promises";
|
|
139
|
+
import { tmpdir } from "os";
|
|
140
|
+
import { join } from "path";
|
|
141
|
+
var markerRoot = join(tmpdir(), "cxclaw-auth-wait");
|
|
142
|
+
var markerTtlMs = 15 * 60 * 1e3;
|
|
143
|
+
var markerName = (sessionId, pid) => `${createHash("sha256").update(sessionId).digest("hex").slice(0, 24)}-${pid}.json`;
|
|
144
|
+
async function withAuthWaitMarker(operation, env = process.env) {
|
|
145
|
+
const sessionId = env.CODEX_SESSION_ID ?? env.CODEX_THREAD_ID;
|
|
146
|
+
if (!sessionId) return operation();
|
|
147
|
+
await mkdir(markerRoot, { recursive: true, mode: 448 });
|
|
148
|
+
const path = join(markerRoot, markerName(sessionId, process.pid));
|
|
149
|
+
await writeFile(path, JSON.stringify({
|
|
150
|
+
sessionId,
|
|
151
|
+
threadId: env.CODEX_THREAD_ID ?? null,
|
|
152
|
+
pid: process.pid,
|
|
153
|
+
expiresAt: Date.now() + markerTtlMs
|
|
154
|
+
}), { mode: 384 });
|
|
155
|
+
try {
|
|
156
|
+
return await operation();
|
|
157
|
+
} finally {
|
|
158
|
+
await rm(path, { force: true }).catch(() => void 0);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
134
162
|
// src/auth.ts
|
|
135
163
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
136
164
|
var loginInFlight;
|
|
137
165
|
function publicAuthStatus(session) {
|
|
138
166
|
return { ok: true, loggedIn: true, email: session.email, channel: session.channel, accessExpiresAt: session.accessExpiry };
|
|
139
167
|
}
|
|
140
|
-
async function
|
|
168
|
+
async function authorizeDevice(channel, runtime) {
|
|
141
169
|
const session = { channel, accessToken: "", refreshToken: "", accessExpiry: 0, employeeId: "", email: "" };
|
|
142
|
-
const start = await
|
|
143
|
-
|
|
170
|
+
const start = await runtime.start(session);
|
|
171
|
+
try {
|
|
172
|
+
await runtime.openBrowser(start.verificationUriComplete);
|
|
173
|
+
runtime.log("\u9489\u9489\u6388\u6743\u9875\u5DF2\u6253\u5F00\uFF0C\u5F53\u524D\u547D\u4EE4\u4F1A\u6301\u7EED\u7B49\u5F85\u6388\u6743\u7ED3\u679C\uFF0C\u65E0\u9700\u56DE\u590D Codex\u3002");
|
|
174
|
+
} catch {
|
|
175
|
+
runtime.log(`\u672A\u80FD\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668\uFF0C\u8BF7\u6253\u5F00\u4EE5\u4E0B\u5730\u5740\u5B8C\u6210\u9489\u9489\u6388\u6743\uFF1A
|
|
144
176
|
${start.verificationUriComplete}
|
|
145
177
|
\u8BBE\u5907\u7801\uFF1A${start.userCode}`);
|
|
146
|
-
|
|
147
|
-
const deadline =
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
await sleep(Math.max(2, start.interval) * 1e3);
|
|
178
|
+
}
|
|
179
|
+
const deadline = runtime.now() + start.expiresIn * 1e3;
|
|
180
|
+
let nextProgressAt = runtime.now() + 15e3;
|
|
181
|
+
while (runtime.now() < deadline) {
|
|
182
|
+
await runtime.sleep(Math.max(2, start.interval) * 1e3);
|
|
152
183
|
try {
|
|
153
|
-
const token = await
|
|
154
|
-
Object.assign(session, { accessToken: token.accessToken, refreshToken: token.refreshToken, accessExpiry: Math.floor(
|
|
155
|
-
await
|
|
156
|
-
|
|
184
|
+
const token = await runtime.poll(session, start.deviceCode);
|
|
185
|
+
Object.assign(session, { accessToken: token.accessToken, refreshToken: token.refreshToken, accessExpiry: Math.floor(runtime.now() / 1e3) + token.expiresIn, employeeId: token.employee.id, email: token.employee.email });
|
|
186
|
+
await runtime.save(session);
|
|
187
|
+
runtime.log("\u9489\u9489\u6388\u6743\u6210\u529F\uFF0C\u7EE7\u7EED\u6267\u884C\u5F53\u524D\u547D\u4EE4\u3002");
|
|
157
188
|
return session;
|
|
158
189
|
} catch (error) {
|
|
159
190
|
if (!(error instanceof HttpError) || error.status !== 428) throw error;
|
|
160
|
-
if (
|
|
161
|
-
|
|
162
|
-
nextProgressAt =
|
|
191
|
+
if (runtime.now() >= nextProgressAt) {
|
|
192
|
+
runtime.log("\u4ECD\u5728\u7B49\u5F85\u9489\u9489\u6388\u6743\u2026\u2026");
|
|
193
|
+
nextProgressAt = runtime.now() + 15e3;
|
|
163
194
|
}
|
|
164
195
|
}
|
|
165
196
|
}
|
|
166
197
|
throw new Error("device authorization expired");
|
|
167
198
|
}
|
|
199
|
+
async function runLogin(channel) {
|
|
200
|
+
return withAuthWaitMarker(() => authorizeDevice(channel, {
|
|
201
|
+
start: (session) => post(session, "/auth/device/start", { clientVersion: VERSION }),
|
|
202
|
+
poll: (session, deviceCode) => post(session, "/auth/device/token", { deviceCode, clientVersion: VERSION }),
|
|
203
|
+
openBrowser: async (url) => {
|
|
204
|
+
await open(url);
|
|
205
|
+
},
|
|
206
|
+
save: saveSession,
|
|
207
|
+
sleep,
|
|
208
|
+
now: Date.now,
|
|
209
|
+
log: (message) => console.error(message)
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
168
212
|
function login(channel) {
|
|
169
213
|
if (!loginInFlight) loginInFlight = runLogin(channel).finally(() => {
|
|
170
214
|
loginInFlight = void 0;
|
|
@@ -247,10 +291,10 @@ import { dirname as dirname2, resolve } from "path";
|
|
|
247
291
|
import { fileURLToPath } from "url";
|
|
248
292
|
|
|
249
293
|
// src/solvea/commands.ts
|
|
250
|
-
import { readFile as
|
|
294
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
251
295
|
|
|
252
296
|
// src/solvea/client.ts
|
|
253
|
-
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
297
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
254
298
|
import { basename, dirname, extname } from "path";
|
|
255
299
|
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
|
256
300
|
var KNOWLEDGE_TYPES = /* @__PURE__ */ new Set(["QA", "LEARN_SELF", "FILE", "WEBSITE", "PRODUCT"]);
|
|
@@ -698,7 +742,7 @@ var SolveaClient = class {
|
|
|
698
742
|
await client.send(new PutObjectCommand({
|
|
699
743
|
Bucket: String(credentials.bucketName),
|
|
700
744
|
Key: key,
|
|
701
|
-
Body: await
|
|
745
|
+
Body: await readFile2(filePath),
|
|
702
746
|
ContentType: contentTypes[extname(filePath).toLowerCase()] ?? "application/octet-stream"
|
|
703
747
|
}));
|
|
704
748
|
return key;
|
|
@@ -715,8 +759,8 @@ var SolveaClient = class {
|
|
|
715
759
|
signal: AbortSignal.timeout(3e4)
|
|
716
760
|
});
|
|
717
761
|
if (!response.ok) throw new Error(`HTTP ${response.status} GET ${path}: ${await response.text()}`);
|
|
718
|
-
await
|
|
719
|
-
await
|
|
762
|
+
await mkdir2(dirname(target), { recursive: true });
|
|
763
|
+
await writeFile2(target, new Uint8Array(await response.arrayBuffer()));
|
|
720
764
|
return target;
|
|
721
765
|
}
|
|
722
766
|
async downloadLabelTemplate(templateType, target) {
|
|
@@ -958,10 +1002,10 @@ var SolveaClient = class {
|
|
|
958
1002
|
};
|
|
959
1003
|
|
|
960
1004
|
// src/solvea/input.ts
|
|
961
|
-
import { readFile as
|
|
1005
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
962
1006
|
async function jsonData(args, required = true) {
|
|
963
1007
|
const file = option(args, "--data-file");
|
|
964
|
-
const raw = file ? await
|
|
1008
|
+
const raw = file ? await readFile3(file, "utf8") : option(args, "--data");
|
|
965
1009
|
if (!raw) {
|
|
966
1010
|
if (required) throw new Error("--data or --data-file is required");
|
|
967
1011
|
return {};
|
|
@@ -972,7 +1016,7 @@ async function jsonData(args, required = true) {
|
|
|
972
1016
|
}
|
|
973
1017
|
async function jsonValue(args) {
|
|
974
1018
|
const file = option(args, "--data-file");
|
|
975
|
-
const raw = file ? await
|
|
1019
|
+
const raw = file ? await readFile3(file, "utf8") : option(args, "--data", true);
|
|
976
1020
|
return JSON.parse(raw);
|
|
977
1021
|
}
|
|
978
1022
|
function requireFlag(args, name, command) {
|
|
@@ -1155,7 +1199,7 @@ function enabled(args, command) {
|
|
|
1155
1199
|
}
|
|
1156
1200
|
async function data2(args) {
|
|
1157
1201
|
const file = option(args, "--data-file");
|
|
1158
|
-
const raw = file ? await
|
|
1202
|
+
const raw = file ? await readFile4(file, "utf8") : option(args, "--data");
|
|
1159
1203
|
if (!raw) throw new Error("--data or --data-file is required");
|
|
1160
1204
|
const parsed = JSON.parse(raw);
|
|
1161
1205
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("request data must be a JSON object");
|
|
@@ -1500,13 +1544,13 @@ async function runResourceCommand(session, resource, action2, args) {
|
|
|
1500
1544
|
|
|
1501
1545
|
// src/doctor.ts
|
|
1502
1546
|
import { access } from "fs/promises";
|
|
1503
|
-
import { join } from "path";
|
|
1547
|
+
import { join as join2 } from "path";
|
|
1504
1548
|
async function diagnose() {
|
|
1505
1549
|
const root2 = packageRoot();
|
|
1506
1550
|
const requiredFiles = [
|
|
1507
1551
|
"package.json"
|
|
1508
1552
|
];
|
|
1509
|
-
await Promise.all(requiredFiles.map((path) => access(
|
|
1553
|
+
await Promise.all(requiredFiles.map((path) => access(join2(root2, path))));
|
|
1510
1554
|
const expectedPluginVersion = process.env.CXCLAW_PLUGIN_VERSION || null;
|
|
1511
1555
|
if (expectedPluginVersion && expectedPluginVersion !== VERSION) {
|
|
1512
1556
|
throw new Error(
|
|
@@ -1558,9 +1602,9 @@ function publicTenant(t) {
|
|
|
1558
1602
|
|
|
1559
1603
|
// src/update.ts
|
|
1560
1604
|
import { spawn } from "child_process";
|
|
1561
|
-
import { mkdir as
|
|
1605
|
+
import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
|
|
1562
1606
|
import { homedir, platform } from "os";
|
|
1563
|
-
import { isAbsolute, join as
|
|
1607
|
+
import { isAbsolute, join as join3 } from "path";
|
|
1564
1608
|
var exec = (command, args, options3 = {}) => new Promise((resolve5, reject) => {
|
|
1565
1609
|
const chunks = [];
|
|
1566
1610
|
const child = spawn(command, args, {
|
|
@@ -1633,11 +1677,11 @@ async function update() {
|
|
|
1633
1677
|
async function installScheduler() {
|
|
1634
1678
|
const invocation = schedulerInvocation();
|
|
1635
1679
|
if (platform() === "darwin") {
|
|
1636
|
-
const dir =
|
|
1637
|
-
const path =
|
|
1680
|
+
const dir = join3(homedir(), "Library/LaunchAgents");
|
|
1681
|
+
const path = join3(dir, "com.solvea.cxclaw.update.plist");
|
|
1638
1682
|
const programArguments = [invocation.command, ...invocation.args].map((value) => `<string>${xml(value)}</string>`).join("");
|
|
1639
|
-
await
|
|
1640
|
-
await
|
|
1683
|
+
await mkdir3(dir, { recursive: true });
|
|
1684
|
+
await writeFile3(
|
|
1641
1685
|
path,
|
|
1642
1686
|
`<?xml version="1.0" encoding="UTF-8"?>
|
|
1643
1687
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -1649,18 +1693,18 @@ async function installScheduler() {
|
|
|
1649
1693
|
return;
|
|
1650
1694
|
}
|
|
1651
1695
|
if (platform() === "linux") {
|
|
1652
|
-
const dir =
|
|
1696
|
+
const dir = join3(homedir(), ".config/systemd/user");
|
|
1653
1697
|
const command = [invocation.command, ...invocation.args].map(systemdArg).join(" ");
|
|
1654
|
-
await
|
|
1655
|
-
await
|
|
1656
|
-
|
|
1698
|
+
await mkdir3(dir, { recursive: true });
|
|
1699
|
+
await writeFile3(
|
|
1700
|
+
join3(dir, "cxclaw-update.service"),
|
|
1657
1701
|
`[Service]
|
|
1658
1702
|
Type=oneshot
|
|
1659
1703
|
ExecStart=${command}
|
|
1660
1704
|
`
|
|
1661
1705
|
);
|
|
1662
|
-
await
|
|
1663
|
-
|
|
1706
|
+
await writeFile3(
|
|
1707
|
+
join3(dir, "cxclaw-update.timer"),
|
|
1664
1708
|
"[Timer]\nOnBootSec=5m\nOnUnitActiveSec=6h\nPersistent=true\n[Install]\nWantedBy=timers.target\n"
|
|
1665
1709
|
);
|
|
1666
1710
|
await exec("systemctl", ["--user", "daemon-reload"]);
|
|
@@ -1699,12 +1743,12 @@ async function installMarketplace(channel) {
|
|
|
1699
1743
|
// src/workflows/runtime.ts
|
|
1700
1744
|
import { execFile } from "child_process";
|
|
1701
1745
|
import { promises as fs } from "fs";
|
|
1702
|
-
import { join as
|
|
1746
|
+
import { join as join4, resolve as resolve2 } from "path";
|
|
1703
1747
|
import { promisify } from "util";
|
|
1704
1748
|
var exec2 = promisify(execFile);
|
|
1705
|
-
var workspace = () => resolve2(process.env.WORKSPACE_DIR ??
|
|
1706
|
-
var root = () =>
|
|
1707
|
-
var dirs = () => ({ output:
|
|
1749
|
+
var workspace = () => resolve2(process.env.WORKSPACE_DIR ?? join4(process.cwd(), "workspace"));
|
|
1750
|
+
var root = () => join4(workspace(), "runtime-inspector");
|
|
1751
|
+
var dirs = () => ({ output: join4(root(), "outputs"), state: join4(root(), "state"), tmp: join4(root(), "tmp") });
|
|
1708
1752
|
var timestamp = () => (/* @__PURE__ */ new Date()).toISOString().replace(/[-:TZ.]/g, "").slice(0, 15);
|
|
1709
1753
|
var readJson = async (path, fallback) => {
|
|
1710
1754
|
try {
|
|
@@ -1740,26 +1784,26 @@ async function prepare(args) {
|
|
|
1740
1784
|
await fs.mkdir(tmp, { recursive: true });
|
|
1741
1785
|
const batches = [];
|
|
1742
1786
|
for (let i = 0; i < records.length; i += batchSize) batches.push(records.slice(i, i + batchSize));
|
|
1743
|
-
await Promise.all(batches.map((batch, i) => writeJson(
|
|
1744
|
-
await writeJson(
|
|
1787
|
+
await Promise.all(batches.map((batch, i) => writeJson(join4(tmp, `batch_${String(i).padStart(3, "0")}.json`), batch)));
|
|
1788
|
+
await writeJson(join4(state2, "inspect.json"), { status: "batched", total_records: records.length, total_batches: batches.length, batch_size: batchSize, source: source ?? "auto-generated", written_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1745
1789
|
return { status: "batched", totalRecords: records.length, totalBatches: batches.length, tmp };
|
|
1746
1790
|
}
|
|
1747
1791
|
async function processBatches() {
|
|
1748
1792
|
const { tmp, output, state: state2 } = dirs();
|
|
1749
|
-
const current = await readJson(
|
|
1793
|
+
const current = await readJson(join4(state2, "inspect.json"), {});
|
|
1750
1794
|
if (current.status !== "batched") throw new Error(`runtime state must be batched, got ${String(current.status ?? "missing")}`);
|
|
1751
1795
|
const names = (await fs.readdir(tmp)).filter((name) => /^batch_\d+\.json$/.test(name)).sort();
|
|
1752
1796
|
if (!names.length) throw new Error(`no batch files found in ${tmp}`);
|
|
1753
1797
|
const records = [];
|
|
1754
|
-
for (const name of names) for (const row of await readJson(
|
|
1755
|
-
const path =
|
|
1798
|
+
for (const name of names) for (const row of await readJson(join4(tmp, name), [])) records.push({ ...row, _processed: true, _summary: `processed: ${JSON.stringify(row).slice(0, 60)}`, _at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1799
|
+
const path = join4(output, `results_${timestamp()}.json`);
|
|
1756
1800
|
await writeJson(path, { total: records.length, processed_at: (/* @__PURE__ */ new Date()).toISOString(), source: current.source, records });
|
|
1757
|
-
await writeJson(
|
|
1801
|
+
await writeJson(join4(state2, "inspect.json"), { status: "done", total_processed: records.length, results_path: path, done_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1758
1802
|
return { status: "done", total: records.length, resultsPath: path };
|
|
1759
1803
|
}
|
|
1760
1804
|
async function state() {
|
|
1761
1805
|
const { state: stateDir2, output, tmp } = dirs();
|
|
1762
|
-
return { state: await readJson(
|
|
1806
|
+
return { state: await readJson(join4(stateDir2, "inspect.json"), null), outputFiles: await fs.readdir(output).catch(() => []), tmpFiles: await fs.readdir(tmp).catch(() => []) };
|
|
1763
1807
|
}
|
|
1764
1808
|
async function verify() {
|
|
1765
1809
|
const snapshot = await state();
|
|
@@ -1786,7 +1830,7 @@ async function sandbox(args) {
|
|
|
1786
1830
|
}
|
|
1787
1831
|
async function model(args) {
|
|
1788
1832
|
if (option(args, "--scenario", true) !== "truncation") throw new Error("only --scenario truncation is supported");
|
|
1789
|
-
const target =
|
|
1833
|
+
const target = join4(dirs().tmp, "truncation_test.md");
|
|
1790
1834
|
return { scenario: "truncation", target, instruction: "Use the Codex write_file tool to write 60+ long technical paragraphs, then inspect SLG model_response stop_reason and output_tokens." };
|
|
1791
1835
|
}
|
|
1792
1836
|
async function runRuntime(action2, args) {
|
|
@@ -1799,9 +1843,9 @@ async function runRuntime(action2, args) {
|
|
|
1799
1843
|
if (action2 === "write-test") {
|
|
1800
1844
|
const { output, state: stateDir2 } = dirs();
|
|
1801
1845
|
const runId = timestamp();
|
|
1802
|
-
await writeJson(
|
|
1803
|
-
const current = await readJson(
|
|
1804
|
-
await writeJson(
|
|
1846
|
+
await writeJson(join4(output, `${runId}_test.json`), { run_id: runId, written_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1847
|
+
const current = await readJson(join4(stateDir2, "inspect.json"), {});
|
|
1848
|
+
await writeJson(join4(stateDir2, "inspect.json"), { ...current, last_run_id: runId, write_count: Number(current.write_count ?? 0) + 1 });
|
|
1805
1849
|
return { ok: true, runId };
|
|
1806
1850
|
}
|
|
1807
1851
|
throw new Error("runtime action must be prepare, process, state, verify, sandbox, model, or write-test");
|
|
@@ -1810,10 +1854,10 @@ async function runRuntime(action2, args) {
|
|
|
1810
1854
|
// src/workflows/solvea-kit.ts
|
|
1811
1855
|
import ExcelJS from "exceljs";
|
|
1812
1856
|
import { promises as fs2 } from "fs";
|
|
1813
|
-
import { dirname as dirname3, join as
|
|
1814
|
-
var workspace2 = () => resolve3(process.env.WORKSPACE_DIR ??
|
|
1815
|
-
var outputDir = (owner) =>
|
|
1816
|
-
var stateDir = (owner) =>
|
|
1857
|
+
import { dirname as dirname3, join as join5, resolve as resolve3 } from "path";
|
|
1858
|
+
var workspace2 = () => resolve3(process.env.WORKSPACE_DIR ?? join5(process.cwd(), "workspace"));
|
|
1859
|
+
var outputDir = (owner) => join5(workspace2(), "outputs", owner);
|
|
1860
|
+
var stateDir = (owner) => join5(workspace2(), "state", owner);
|
|
1817
1861
|
var now = () => (/* @__PURE__ */ new Date()).toISOString().replace(/[-:TZ.]/g, "").slice(0, 15);
|
|
1818
1862
|
var readJson2 = async (path) => JSON.parse(await fs2.readFile(path, "utf8"));
|
|
1819
1863
|
var writeJson2 = async (path, value) => {
|
|
@@ -1843,7 +1887,7 @@ function labelsFrom(value) {
|
|
|
1843
1887
|
async function generateLabelImport(args) {
|
|
1844
1888
|
const input = option(args, "--input") ?? option(args, "--data-file", true);
|
|
1845
1889
|
const init = args.includes("--init");
|
|
1846
|
-
const output = option(args, "--output") ??
|
|
1890
|
+
const output = option(args, "--output") ?? join5(stateDir("solvea-kit"), init ? "labels_init.xlsx" : "labels_update.xlsx");
|
|
1847
1891
|
const labels = labelsFrom(await readJson2(input));
|
|
1848
1892
|
const workbook = new ExcelJS.Workbook();
|
|
1849
1893
|
const sheet = workbook.addWorksheet("labels");
|
|
@@ -1883,9 +1927,9 @@ async function classifyIntents(args) {
|
|
|
1883
1927
|
if (!noExcel && !option(args, "--xlsx")) throw new Error("provide --xlsx or --no-excel");
|
|
1884
1928
|
const intents = Object.entries(TAXONOMY).map(([name, l2s]) => ({ name, definition: `Customer requests and issues related to ${name}.`, count: 0, pct: 0, sub_intents: l2s.map((l2) => ({ name: l2, definition: "", count: 0, pct: 0, samples: [], is_new: false })), samples: [], is_new: false }));
|
|
1885
1929
|
const result = { meta: { run_id: runId, total: 0, classified: 0, n_known: intents.length, n_new: 0, mode: noExcel ? "no-excel" : "structured" }, intents, new_intents: [] };
|
|
1886
|
-
const resultPath =
|
|
1930
|
+
const resultPath = join5(outputDir(owner), `results_${runId}.json`);
|
|
1887
1931
|
await writeJson2(resultPath, result);
|
|
1888
|
-
await writeJson2(
|
|
1932
|
+
await writeJson2(join5(stateDir(owner), "progress.json"), { status: "ready", run_id: runId, results_path: resultPath, updated_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1889
1933
|
return { ...result.meta, resultsPath: resultPath };
|
|
1890
1934
|
}
|
|
1891
1935
|
async function runSolveaKitWorkflow(action2, args) {
|
|
@@ -1895,7 +1939,7 @@ async function runSolveaKitWorkflow(action2, args) {
|
|
|
1895
1939
|
}
|
|
1896
1940
|
|
|
1897
1941
|
// src/workflows/support.ts
|
|
1898
|
-
import { readFile as
|
|
1942
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
1899
1943
|
import { basename as basename3 } from "path";
|
|
1900
1944
|
var safe = (value) => encodeURIComponent(value);
|
|
1901
1945
|
var docsMcpUrl = process.env.CXCLAW_DOCS_MCP_URL ?? "https://solvea-39f11a36.mintlify.app/mcp";
|
|
@@ -1970,7 +2014,7 @@ async function runSupport(session, action2, args) {
|
|
|
1970
2014
|
const attachments = [];
|
|
1971
2015
|
for (const file of options2(args, "--attachment")) {
|
|
1972
2016
|
const form = new FormData();
|
|
1973
|
-
form.append("file", new Blob([await
|
|
2017
|
+
form.append("file", new Blob([await readFile5(file)]), basename3(file));
|
|
1974
2018
|
const tenant = session.tenant;
|
|
1975
2019
|
const base = tenant?.businessUrl?.replace(/\/$/, "");
|
|
1976
2020
|
if (!tenant || !base || !tenant.businessToken) throw new Error("no tenant selected");
|
|
@@ -2001,24 +2045,24 @@ async function runSupport(session, action2, args) {
|
|
|
2001
2045
|
}
|
|
2002
2046
|
|
|
2003
2047
|
// src/workflows/common.ts
|
|
2004
|
-
import { mkdir as
|
|
2005
|
-
import { dirname as dirname4, join as
|
|
2048
|
+
import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
|
|
2049
|
+
import { dirname as dirname4, join as join6, resolve as resolve4 } from "path";
|
|
2006
2050
|
import { homedir as homedir2 } from "os";
|
|
2007
|
-
var workspaceRoot = () => resolve4(process.env.CXCLAW_WORKSPACE ??
|
|
2008
|
-
var stateRoot = (domain) => resolve4(process.env.CXCLAW_STATE_DIR ??
|
|
2009
|
-
var outputRoot = (domain) => resolve4(process.env.CXCLAW_OUTPUT_DIR ??
|
|
2051
|
+
var workspaceRoot = () => resolve4(process.env.CXCLAW_WORKSPACE ?? join6(homedir2(), ".cxclaw", "workspace"));
|
|
2052
|
+
var stateRoot = (domain) => resolve4(process.env.CXCLAW_STATE_DIR ?? join6(workspaceRoot(), "state"), domain);
|
|
2053
|
+
var outputRoot = (domain) => resolve4(process.env.CXCLAW_OUTPUT_DIR ?? join6(workspaceRoot(), "outputs"), domain);
|
|
2010
2054
|
async function ensureParent(file) {
|
|
2011
|
-
await
|
|
2055
|
+
await mkdir4(dirname4(file), { recursive: true });
|
|
2012
2056
|
}
|
|
2013
2057
|
async function ensureDir(directory) {
|
|
2014
|
-
await
|
|
2058
|
+
await mkdir4(directory, { recursive: true });
|
|
2015
2059
|
}
|
|
2016
2060
|
async function readJson3(file) {
|
|
2017
|
-
return JSON.parse(await
|
|
2061
|
+
return JSON.parse(await readFile6(file, "utf8"));
|
|
2018
2062
|
}
|
|
2019
2063
|
async function writeJson3(file, value) {
|
|
2020
2064
|
await ensureParent(file);
|
|
2021
|
-
await
|
|
2065
|
+
await writeFile4(file, `${JSON.stringify(value, null, 2)}
|
|
2022
2066
|
`, "utf8");
|
|
2023
2067
|
}
|
|
2024
2068
|
function intOption(args, name, fallback) {
|
|
@@ -2036,7 +2080,7 @@ function htmlEscape(value) {
|
|
|
2036
2080
|
}
|
|
2037
2081
|
async function writeHtml(file, title, body) {
|
|
2038
2082
|
await ensureParent(file);
|
|
2039
|
-
await
|
|
2083
|
+
await writeFile4(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");
|
|
2040
2084
|
}
|
|
2041
2085
|
function asRows(value) {
|
|
2042
2086
|
if (Array.isArray(value)) return value.filter((item) => item && typeof item === "object");
|
|
@@ -2243,13 +2287,13 @@ async function runLocalAgentWorkflow(action2, args) {
|
|
|
2243
2287
|
}
|
|
2244
2288
|
async function writeFileText(file, content) {
|
|
2245
2289
|
await ensureParent(file);
|
|
2246
|
-
const { writeFile:
|
|
2247
|
-
await
|
|
2290
|
+
const { writeFile: writeFile5 } = await import("fs/promises");
|
|
2291
|
+
await writeFile5(file, `${content}
|
|
2248
2292
|
`, "utf8");
|
|
2249
2293
|
}
|
|
2250
2294
|
|
|
2251
2295
|
// src/workflows/tester.ts
|
|
2252
|
-
import { join as
|
|
2296
|
+
import { join as join7 } from "path";
|
|
2253
2297
|
import ExcelJS2 from "exceljs";
|
|
2254
2298
|
async function casesFrom(file) {
|
|
2255
2299
|
if (file.endsWith(".json")) return asRows(await readJson3(file));
|
|
@@ -2271,7 +2315,7 @@ async function casesFrom(file) {
|
|
|
2271
2315
|
}
|
|
2272
2316
|
async function createSheet(args) {
|
|
2273
2317
|
const cases = option(args, "--cases-file") ? await casesFrom(option(args, "--cases-file")) : [];
|
|
2274
|
-
const output = option(args, "--output") ??
|
|
2318
|
+
const output = option(args, "--output") ?? join7(outputRoot("agent-tester"), `cases_${Date.now()}.xlsx`);
|
|
2275
2319
|
await ensureParent(output);
|
|
2276
2320
|
const workbook = new ExcelJS2.Workbook();
|
|
2277
2321
|
const sheet = workbook.addWorksheet("cases");
|
|
@@ -2297,7 +2341,7 @@ async function runAgentTester(session, action2, args) {
|
|
|
2297
2341
|
}
|
|
2298
2342
|
|
|
2299
2343
|
// src/workflows/delivery.ts
|
|
2300
|
-
import { join as
|
|
2344
|
+
import { join as join8 } from "path";
|
|
2301
2345
|
import ExcelJS3 from "exceljs";
|
|
2302
2346
|
var KNOWN_INTENTS = ["Product Recommendation", "Discount Inquiry", "Price Inquiry", "Stock Availability", "Payment Issues", "Request Invoice", "Modify Order", "Check Order Status", "Cancel Order", "Track Shipment", "Shipping Exception", "Missing Items", "Wrong Items Received", "Damaged Package", "Product Usage", "Return and exchange"];
|
|
2303
2347
|
async function loadRows(file) {
|
|
@@ -2331,7 +2375,7 @@ async function writeRows(file, rows) {
|
|
|
2331
2375
|
const keys = [...new Set(rows.flatMap((row) => Object.keys(row)))];
|
|
2332
2376
|
sheet.addRow(keys);
|
|
2333
2377
|
rows.forEach((row) => sheet.addRow(keys.map((key) => row[key] ?? "")));
|
|
2334
|
-
await ensureDir(
|
|
2378
|
+
await ensureDir(join8(file, ".."));
|
|
2335
2379
|
await workbook.xlsx.writeFile(file);
|
|
2336
2380
|
}
|
|
2337
2381
|
function classify(text) {
|
|
@@ -2352,13 +2396,13 @@ async function classifyCases(args, domain) {
|
|
|
2352
2396
|
const intents = [...grouped.entries()].map(([name, items]) => ({ name, definition: `Customer requests and issues related to ${name}.`, count: items.length, pct: rows.length ? items.length / rows.length : 0, samples: items.slice(0, 5), is_new: name === "New Intent" }));
|
|
2353
2397
|
const runId = option(args, "--run-id") ?? Date.now().toString();
|
|
2354
2398
|
const result = { meta: { run_id: runId, total: rows.length, classified: rows.length, mode: noExcel ? "no-excel" : "structured", domain }, intents, new_intents: intents.filter((item) => item.is_new) };
|
|
2355
|
-
const out = option(args, "--results") ??
|
|
2399
|
+
const out = option(args, "--results") ?? join8(outputRoot(domain), `results_${runId}.json`);
|
|
2356
2400
|
await writeJson3(out, result);
|
|
2357
|
-
await writeJson3(
|
|
2401
|
+
await writeJson3(join8(stateRoot(domain), "progress.json"), { status: "classified", run_id: runId, results_path: out, updated_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2358
2402
|
return { ...result.meta, resultsPath: out };
|
|
2359
2403
|
}
|
|
2360
2404
|
async function plan(domain, args) {
|
|
2361
|
-
const file =
|
|
2405
|
+
const file = join8(stateRoot(domain), "progress.json");
|
|
2362
2406
|
let current = {};
|
|
2363
2407
|
try {
|
|
2364
2408
|
current = await readJson3(file);
|
|
@@ -2382,7 +2426,7 @@ async function plan(domain, args) {
|
|
|
2382
2426
|
async function runBatch(client, args, domain) {
|
|
2383
2427
|
const input = option(args, "--input") ?? option(args, "--test-file", true);
|
|
2384
2428
|
const rows = await loadRows(input);
|
|
2385
|
-
const output = option(args, "--output") ??
|
|
2429
|
+
const output = option(args, "--output") ?? join8(outputRoot(domain), `test_results_${Date.now()}.json`);
|
|
2386
2430
|
const workers = Math.max(1, Math.min(intOption(args, "--workers", 4), 16));
|
|
2387
2431
|
const results = [];
|
|
2388
2432
|
let cursor = 0;
|
|
@@ -2406,7 +2450,7 @@ async function runBatch(client, args, domain) {
|
|
|
2406
2450
|
return { output, total: results.length, succeeded: results.filter((row) => row.ok).length, failed: results.filter((row) => !row.ok).length };
|
|
2407
2451
|
}
|
|
2408
2452
|
async function createAgents(client, args, domain) {
|
|
2409
|
-
const state2 = await readJson3(
|
|
2453
|
+
const state2 = await readJson3(join8(stateRoot(domain), "progress.json")).catch(() => ({}));
|
|
2410
2454
|
const resultsPath = option(args, "--results") ?? (typeof state2.results_path === "string" ? state2.results_path : void 0);
|
|
2411
2455
|
if (!resultsPath) throw new Error("classification results are required");
|
|
2412
2456
|
const result = await readJson3(resultsPath);
|
|
@@ -2417,12 +2461,12 @@ async function createAgents(client, args, domain) {
|
|
|
2417
2461
|
return { created, count: created.length };
|
|
2418
2462
|
}
|
|
2419
2463
|
async function renderReport(args, domain) {
|
|
2420
|
-
const progress = await readJson3(
|
|
2464
|
+
const progress = await readJson3(join8(stateRoot(domain), "progress.json")).catch(() => ({}));
|
|
2421
2465
|
const resultsFile = option(args, "--results") ?? option(args, "--input") ?? (typeof progress.results_path === "string" ? progress.results_path : void 0);
|
|
2422
2466
|
if (!resultsFile) throw new Error("--results/--input is required, or run classify first");
|
|
2423
2467
|
const value = await readJson3(resultsFile);
|
|
2424
2468
|
const rows = asRows(value.results ?? value.items ?? value);
|
|
2425
|
-
const output = option(args, "--html") ?? option(args, "--output") ??
|
|
2469
|
+
const output = option(args, "--html") ?? option(args, "--output") ?? join8(outputRoot(domain), `${domain}_report.html`);
|
|
2426
2470
|
const body = `<p>Total: ${rows.length}</p>${rows.map((row, index) => `<div class="card"><h2>${index + 1}. ${htmlEscape(row.intent ?? row.question ?? row.message ?? "Case")}</h2><pre>${htmlEscape(JSON.stringify(row, null, 2))}</pre></div>`).join("")}`;
|
|
2427
2471
|
await writeHtml(output, option(args, "--title") ?? `${domain} report`, body);
|
|
2428
2472
|
const md = option(args, "--md");
|
|
@@ -2433,12 +2477,12 @@ Total: ${rows.length}
|
|
|
2433
2477
|
return { output, count: rows.length };
|
|
2434
2478
|
}
|
|
2435
2479
|
async function genExcel(args, domain) {
|
|
2436
|
-
const progress = await readJson3(
|
|
2480
|
+
const progress = await readJson3(join8(stateRoot(domain), "progress.json")).catch(() => ({}));
|
|
2437
2481
|
const input = option(args, "--results") ?? option(args, "--items") ?? (typeof progress.results_path === "string" ? progress.results_path : void 0);
|
|
2438
2482
|
if (!input) throw new Error("--results or --items is required, or run classify first");
|
|
2439
2483
|
const value = await readJson3(input);
|
|
2440
2484
|
const rows = asRows(value);
|
|
2441
|
-
const output = option(args, "--output") ??
|
|
2485
|
+
const output = option(args, "--output") ?? join8(outputRoot(domain), `${domain}_results.xlsx`);
|
|
2442
2486
|
await writeRows(output, rows);
|
|
2443
2487
|
return { output, count: rows.length };
|
|
2444
2488
|
}
|
|
@@ -2478,7 +2522,7 @@ async function runDomain(session, domain, action2, args) {
|
|
|
2478
2522
|
}
|
|
2479
2523
|
|
|
2480
2524
|
// src/workflows/kb-builder.ts
|
|
2481
|
-
import { join as
|
|
2525
|
+
import { join as join9 } from "path";
|
|
2482
2526
|
import ExcelJS4 from "exceljs";
|
|
2483
2527
|
var clean = (value) => String(value ?? "").replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
2484
2528
|
var absolute = (href, base) => {
|
|
@@ -2512,7 +2556,7 @@ async function crawl(args) {
|
|
|
2512
2556
|
}
|
|
2513
2557
|
}
|
|
2514
2558
|
const result = { url, mode: option(args, "--mode") ?? "auto", pages, fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), total: pages.length };
|
|
2515
|
-
const output = option(args, "--output") ??
|
|
2559
|
+
const output = option(args, "--output") ?? join9(outputRoot("kb-builder"), "crawl_result.json");
|
|
2516
2560
|
await writeJson3(output, result);
|
|
2517
2561
|
return { ...result, output };
|
|
2518
2562
|
}
|
|
@@ -2527,13 +2571,13 @@ async function directoryPlan(args) {
|
|
|
2527
2571
|
}
|
|
2528
2572
|
const tree = [...groups.entries()].map(([name, pages]) => ({ name, children: pages.slice(0, 100).map((page2) => ({ name: String(page2.title ?? page2.url), sourceUrl: page2.url })) }));
|
|
2529
2573
|
const result = { source: option(args, "--crawl-result"), generatedAt: (/* @__PURE__ */ new Date()).toISOString(), tree, confirmed: false };
|
|
2530
|
-
const output = option(args, "--output") ??
|
|
2574
|
+
const output = option(args, "--output") ?? join9(outputRoot("kb-builder"), "directory_plan.json");
|
|
2531
2575
|
await writeJson3(output, result);
|
|
2532
|
-
if (flag(args, "--write-confirmed-tree")) await writeJson3(
|
|
2576
|
+
if (flag(args, "--write-confirmed-tree")) await writeJson3(join9(stateRoot("kb-builder"), "confirmed_tree.json"), { ...result, confirmed: true });
|
|
2533
2577
|
return { output, directories: tree.length, pages: rows.length, tree };
|
|
2534
2578
|
}
|
|
2535
2579
|
async function createDirectories(client, args) {
|
|
2536
|
-
const file = option(args, "--tree-file") ??
|
|
2580
|
+
const file = option(args, "--tree-file") ?? join9(stateRoot("kb-builder"), "confirmed_tree.json");
|
|
2537
2581
|
const value = await readJson3(file);
|
|
2538
2582
|
const tree = Array.isArray(value.tree) ? value.tree : Array.isArray(value) ? value : [];
|
|
2539
2583
|
if (!tree.length) throw new Error("confirmed directory tree is empty");
|
|
@@ -2556,12 +2600,12 @@ async function createDirectories(client, args) {
|
|
|
2556
2600
|
async function extractWebQa(args) {
|
|
2557
2601
|
const input = await readJson3(option(args, "--crawl-result", true));
|
|
2558
2602
|
const pages = asRows(input.pages ?? input).slice(0, intOption(args, "--max-pages", 80));
|
|
2559
|
-
const outputDir2 = option(args, "--output-dir") ??
|
|
2603
|
+
const outputDir2 = option(args, "--output-dir") ?? join9(outputRoot("kb-builder"), "qa_parts");
|
|
2560
2604
|
await ensureDir(outputDir2);
|
|
2561
2605
|
const parts = pages.map((page2, index) => ({ question: String(page2.title ?? page2.url), answer: String(page2.text ?? "").slice(0, intOption(args, "--answer-limit", 3500)), sourceUrl: page2.url, sourceTitle: page2.title, index }));
|
|
2562
2606
|
const files = [];
|
|
2563
2607
|
for (const [index, part] of parts.entries()) {
|
|
2564
|
-
const file =
|
|
2608
|
+
const file = join9(outputDir2, `part_${String(index + 1).padStart(4, "0")}.json`);
|
|
2565
2609
|
await writeJson3(file, { items: [part] });
|
|
2566
2610
|
files.push(file);
|
|
2567
2611
|
}
|
|
@@ -2627,9 +2671,9 @@ async function importQa(client, args) {
|
|
|
2627
2671
|
items.push(...Array.isArray(value) ? value : value.items && Array.isArray(value.items) ? value.items : [value]);
|
|
2628
2672
|
}
|
|
2629
2673
|
if (fromDir) {
|
|
2630
|
-
const { readdir } = await import("fs/promises");
|
|
2631
|
-
for (const file of (await
|
|
2632
|
-
const value = await readJson3(
|
|
2674
|
+
const { readdir: readdir2 } = await import("fs/promises");
|
|
2675
|
+
for (const file of (await readdir2(fromDir)).filter((name) => name.endsWith(".json")).sort()) {
|
|
2676
|
+
const value = await readJson3(join9(fromDir, file));
|
|
2633
2677
|
items.push(...Array.isArray(value) ? value : value.items && Array.isArray(value.items) ? value.items : [value]);
|
|
2634
2678
|
}
|
|
2635
2679
|
}
|
|
@@ -2637,7 +2681,7 @@ async function importQa(client, args) {
|
|
|
2637
2681
|
if (!Number.isSafeInteger(maxDepth) || maxDepth <= 0) throw new Error("--max-depth must be a positive integer");
|
|
2638
2682
|
const normalized = validateQaItems(items, maxDepth);
|
|
2639
2683
|
const plan2 = { items, count: items.length, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2640
|
-
const planFile =
|
|
2684
|
+
const planFile = join9(outputRoot("kb-builder"), `qa_import_plan_${Date.now()}.json`);
|
|
2641
2685
|
await writeJson3(planFile, plan2);
|
|
2642
2686
|
if (flag(args, "--dry-run")) return { dryRun: true, planFile, count: items.length };
|
|
2643
2687
|
const directories = flattenDirectories(asRows(await client.listDirectories()));
|
|
@@ -2712,7 +2756,7 @@ async function importTemplates(client, args) {
|
|
|
2712
2756
|
});
|
|
2713
2757
|
}
|
|
2714
2758
|
const items = rows.map((row) => ({ name: String(row.name ?? row.Name ?? "").trim(), content: String(row.content ?? row.Content ?? row.template ?? ""), status: row.status ?? row.Status ?? "ENABLED", scene: row.scene ?? row.Scene })).filter((item) => item.name && item.content);
|
|
2715
|
-
const output = option(args, "--output") ??
|
|
2759
|
+
const output = option(args, "--output") ?? join9(outputRoot("kb-builder"), `template_import_plan_${Date.now()}.json`);
|
|
2716
2760
|
await writeJson3(output, { items, count: items.length });
|
|
2717
2761
|
if (flag(args, "--dry-run")) return { dryRun: true, output, count: items.length };
|
|
2718
2762
|
const results = [];
|
|
@@ -2727,13 +2771,13 @@ async function exportKnowledge(client, args) {
|
|
|
2727
2771
|
const value = await client.listKnowledge({ directoryId: id, keyword: option(args, "--keyword") ?? "", type: option(args, "--type") ?? "ALL", pageSize: intOption(args, "--page-size", 1e3) });
|
|
2728
2772
|
rows.push(...asRows(value).map((row) => ({ ...row, directoryId: id, directoryName: directories.find((dir) => Number(dir.id ?? dir.directoryId) === id)?.name ?? "" })));
|
|
2729
2773
|
}
|
|
2730
|
-
const output = option(args, "--output") ??
|
|
2774
|
+
const output = option(args, "--output") ?? join9(outputRoot("kb-builder"), `knowledge_${Date.now()}.xlsx`);
|
|
2731
2775
|
const workbook = new ExcelJS4.Workbook();
|
|
2732
2776
|
const sheet = workbook.addWorksheet("knowledge");
|
|
2733
2777
|
const keys = [...new Set(rows.flatMap((row) => Object.keys(row)))];
|
|
2734
2778
|
sheet.addRow(keys);
|
|
2735
2779
|
for (const row of rows) sheet.addRow(keys.map((key) => row[key] ?? ""));
|
|
2736
|
-
await ensureDir(
|
|
2780
|
+
await ensureDir(join9(output, ".."));
|
|
2737
2781
|
await workbook.xlsx.writeFile(output);
|
|
2738
2782
|
return { output, count: rows.length };
|
|
2739
2783
|
}
|
|
@@ -2774,7 +2818,7 @@ async function extractTicketQa(args) {
|
|
|
2774
2818
|
const answer = String(record[replyCol] ?? "").trim();
|
|
2775
2819
|
if (question && question !== `${brand}:`) items.push({ question, answer, sourceRow: number });
|
|
2776
2820
|
});
|
|
2777
|
-
const output = option(args, "--output") ??
|
|
2821
|
+
const output = option(args, "--output") ?? join9(outputRoot("kb-builder"), `ticket_qa_${Date.now()}.json`);
|
|
2778
2822
|
const result = { brand, count: items.length, items };
|
|
2779
2823
|
await writeJson3(output, result);
|
|
2780
2824
|
if (flag(args, "--dry-run")) return { dryRun: true, output, count: items.length };
|
|
@@ -2782,12 +2826,12 @@ async function extractTicketQa(args) {
|
|
|
2782
2826
|
}
|
|
2783
2827
|
|
|
2784
2828
|
// src/workflows/desk.ts
|
|
2785
|
-
import { join as
|
|
2829
|
+
import { join as join10 } from "path";
|
|
2786
2830
|
import ExcelJS5 from "exceljs";
|
|
2787
2831
|
var taskId = (args) => option(args, "--task-id") ?? (/* @__PURE__ */ new Date()).toISOString().replace(/[-:.TZ]/g, "").slice(0, 14);
|
|
2788
|
-
var taskDir = (args) =>
|
|
2832
|
+
var taskDir = (args) => join10(stateRoot("solvea-desk"), taskId(args));
|
|
2789
2833
|
async function saveDatasource(dir, name, rows) {
|
|
2790
|
-
const file =
|
|
2834
|
+
const file = join10(dir, name);
|
|
2791
2835
|
await ensureParent(file);
|
|
2792
2836
|
const workbook = new ExcelJS5.Workbook();
|
|
2793
2837
|
const sheet = workbook.addWorksheet("data");
|
|
@@ -2810,7 +2854,7 @@ async function fetchTickets(client, args) {
|
|
|
2810
2854
|
if (pageRows.length < pageSize2) break;
|
|
2811
2855
|
}
|
|
2812
2856
|
const output = await saveDatasource(dir, "datasource.xlsx", rows);
|
|
2813
|
-
await writeJson3(
|
|
2857
|
+
await writeJson3(join10(dir, "metadata.json"), { taskId: taskId(args), rows: rows.length, output });
|
|
2814
2858
|
return { taskId: taskId(args), output, count: rows.length };
|
|
2815
2859
|
}
|
|
2816
2860
|
async function fetchSources(client, args) {
|
|
@@ -2821,7 +2865,7 @@ async function fetchSources(client, args) {
|
|
|
2821
2865
|
const rows = asRows(value).slice(0, intOption(args, "--max-rows", 1e4));
|
|
2822
2866
|
sources.tickets = rows;
|
|
2823
2867
|
const output = await saveDatasource(dir, "datasource.xlsx", rows);
|
|
2824
|
-
await writeJson3(
|
|
2868
|
+
await writeJson3(join10(dir, "sources.json"), sources);
|
|
2825
2869
|
return { taskId: taskId(args), output, count: rows.length };
|
|
2826
2870
|
}
|
|
2827
2871
|
async function tags(client, action2, args) {
|
|
@@ -2848,10 +2892,10 @@ async function tags(client, action2, args) {
|
|
|
2848
2892
|
throw new Error(`unknown tag action: ${action2}`);
|
|
2849
2893
|
}
|
|
2850
2894
|
async function renderReport2(args) {
|
|
2851
|
-
const dir = taskDir(args), dataFile = option(args, "--report-data") ??
|
|
2895
|
+
const dir = taskDir(args), dataFile = option(args, "--report-data") ?? join10(dir, "report_data.json"), notesFile = option(args, "--notes") ?? join10(dir, "calculation_notes.json");
|
|
2852
2896
|
const data3 = await readJson3(dataFile);
|
|
2853
2897
|
if (flag(args, "--validate-only")) return { valid: true, dataFile };
|
|
2854
|
-
const output = option(args, "--output") ??
|
|
2898
|
+
const output = option(args, "--output") ?? join10(outputRoot("solvea-desk"), taskId(args), `${option(args, "--report-slug") ?? "report"}.html`);
|
|
2855
2899
|
const body = `<pre>${htmlEscape(JSON.stringify({ data: data3, notes: await readJson3(notesFile).catch(() => ({})) }, null, 2))}</pre>`;
|
|
2856
2900
|
await writeHtml(output, option(args, "--report-slug") ?? "Solvea Desk report", body);
|
|
2857
2901
|
return { output, valid: true };
|
|
@@ -2971,7 +3015,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
2971
3015
|
if (flag(args, "--scheduled")) await new Promise((resolve5) => setTimeout(resolve5, Math.random() * 18e5));
|
|
2972
3016
|
return print(await update());
|
|
2973
3017
|
}
|
|
2974
|
-
if (command === "commands" || command === "capabilities") console.log(await
|
|
3018
|
+
if (command === "commands" || command === "capabilities") console.log(await readFile7(new URL("../capabilities.lock.json", import.meta.url), "utf8"));
|
|
2975
3019
|
else throw new Error(`unknown command: ${command}`);
|
|
2976
3020
|
}
|
|
2977
3021
|
main().catch((error) => {
|